Ich weiß, dass Python das Überladen von Methoden nicht unterstützt, aber ich bin auf ein Problem gestoßen, das ich scheinbar nicht auf nette pythonische Weise lösen kann.
Ich mache ein Spiel, in dem ein Charakter eine Vielzahl von Kugeln abschießen muss, aber wie schreibe ich verschiedene Funktionen zum Erstellen dieser Kugeln? Angenommen, ich habe eine Funktion, die eine Kugel erzeugt, die sich mit einer bestimmten Geschwindigkeit von Punkt A nach B bewegt. Ich würde eine Funktion wie diese schreiben:
def add_bullet(sprite, start, headto, speed):
... Code ...
Aber ich möchte andere Funktionen zum Erstellen von Aufzählungszeichen schreiben wie:
def add_bullet(sprite, start, direction, speed):
def add_bullet(sprite, start, headto, spead, acceleration):
def add_bullet(sprite, script): # For bullets that are controlled by a script
def add_bullet(sprite, curve, speed): # for bullets with curved paths
... And so on ...
Und so weiter mit vielen Variationen. Gibt es einen besseren Weg, dies zu tun, ohne so viele Schlüsselwortargumente zu verwenden, dass es schnell hässlich wird? Jede Funktion Umbenennen ist ziemlich schlecht auch , weil Sie entweder add_bullet1
, add_bullet2
oder add_bullet_with_really_long_name
.
Um einige Antworten zu finden:
Nein, ich kann keine Bullet-Klassenhierarchie erstellen, da dies zu langsam ist. Der eigentliche Code zum Verwalten von Aufzählungszeichen befindet sich in C, und meine Funktionen sind Wrapper für die C-API.
Ich kenne die Schlüsselwortargumente, aber das Überprüfen auf alle möglichen Kombinationen von Parametern wird ärgerlich, aber Standardargumente helfen bei der Auswahl
acceleration=0
quelle
default value + if + else
, um das gleiche wie C ++ zu tun. Dies ist eines der wenigen Dinge, die C ++ besser lesbar macht als Python ...script, curve
sind, haben sie einen gemeinsamen Vorfahren, welche Methoden sie unterstützen. Beim Entenschreiben liegt es an Ihnen, für das Klassendesign herauszufinden, welche Methoden sie unterstützen müssen.Script
Unterstützt vermutlich eine Art zeitschrittbasierten Rückruf (aber welches Objekt sollte es zurückgeben? Die Position zu diesem Zeitschritt? Die Flugbahn zu diesem Zeitschritt?). Vermutlichstart, direction, speed
undstart, headto, spead, acceleration
beide beschreiben Arten von Trajektorien, aber es liegt wieder an Ihnen, die empfangende Klasse so zu gestalten, dass Sie wissen, wie Sie sie entpacken und verarbeiten können.Antworten:
Was Sie verlangen, wird als Mehrfachversand bezeichnet . Siehe Julia- Sprachbeispiele, die verschiedene Arten von Versendungen demonstrieren.
Bevor wir uns das ansehen, werden wir uns zunächst damit befassen, warum Überladung in Python nicht wirklich das ist, was Sie wollen.
Warum nicht überladen?
Zunächst muss man das Konzept der Überladung verstehen und wissen, warum es nicht auf Python anwendbar ist.
Python ist eine dynamisch typisierte Sprache, daher gilt das Konzept der Überladung einfach nicht für sie. Es ist jedoch nicht alles verloren, da wir zur Laufzeit solche alternativen Funktionen erstellen können :
Wir sollten also in der Lage sein, Multimethoden in Python auszuführen - oder, wie es alternativ heißt: Mehrfachversand .
Mehrfachversand
Die Multimethoden werden auch als Mehrfachversand bezeichnet :
Python unterstützt diese aus der Box 1 , aber, wie es geschieht, gibt es ein ausgezeichnetes Python - Paket namens multipledispatch , die das genau funktioniert.
Lösung
So können wir das Multipledispatch 2- Paket verwenden, um Ihre Methoden zu implementieren:
1. Python 3 unterstützt derzeit den Einzelversand.
2. Achten Sie darauf, dass Sie in einer Umgebung mit mehreren Threads keinen Mehrfachversand verwenden. Andernfalls tritt ein seltsames Verhalten auf.
quelle
speed
kann sich in der Mitte der Funktion ändern, wenn ein anderer Thread seinen eigenen Wert von festlegtspeed
) !!! Es dauerte lange, bis mir klar wurde, dass die Bibliothek der Schuldige war.Python unterstützt das "Überladen von Methoden", wenn Sie es präsentieren. Tatsächlich ist das, was Sie gerade beschreiben, in Python auf so viele verschiedene Arten trivial zu implementieren, aber ich würde folgendermaßen vorgehen:
Im obigen Code
default
ist ein plausibler Standardwert für diese Argumente oderNone
. Sie können die Methode dann nur mit den Argumenten aufrufen, an denen Sie interessiert sind, und Python verwendet die Standardwerte.Sie könnten auch so etwas tun:
Eine andere Alternative besteht darin, die gewünschte Funktion direkt mit der Klasse oder Instanz zu verknüpfen:
Ein weiterer Weg ist die Verwendung eines abstrakten Fabrikmusters:
quelle
if sprite and script and not start and not direction and not speed...
Nur um zu wissen, dass es sich um eine bestimmte Aktion handelt. weil ein Aufrufer die Funktion aufrufen kann, die alle verfügbaren Parameter bereitstellt. Definieren Sie beim Überladen für Sie die genauen Sätze relevanter Parameter.Sie können die "Roll-Your-Own" -Lösung zur Funktionsüberladung verwenden. Dieser ist aus Guido van Rossums Artikel über Multimethoden kopiert (da es in Python kaum einen Unterschied zwischen mm und Überladung gibt):
Die Verwendung wäre
Die derzeit restriktivsten Einschränkungen sind:
quelle
Eine mögliche Option ist die Verwendung des hier beschriebenen Multipledispatch-Moduls: http://matthewrocklin.com/blog/work/2014/02/25/Multiple-Dispatch
Anstatt dies zu tun:
Du kannst das:
Mit der daraus resultierenden Verwendung:
quelle
In Python 3.4 wurde PEP-0443 hinzugefügt . Generische Funktionen für den Einzelversand .
Hier ist eine kurze API-Beschreibung von PEP.
Um eine generische Funktion zu definieren, dekorieren Sie sie mit dem Dekorator @singledispatch. Beachten Sie, dass der Versand für den Typ des ersten Arguments erfolgt. Erstellen Sie Ihre Funktion entsprechend:
Verwenden Sie das Attribut register () der generischen Funktion, um der Funktion überladene Implementierungen hinzuzufügen. Dies ist ein Dekorator, der einen Typparameter verwendet und eine Funktion dekoriert, die die Operation für diesen Typ implementiert:
quelle
Diese Art von Verhalten wird normalerweise (in OOP-Sprachen) mithilfe von Polymorphismus gelöst. Jede Art von Kugel wäre dafür verantwortlich zu wissen, wie sie sich bewegt. Zum Beispiel:
Übergeben Sie so viele Argumente wie möglich an die vorhandene c_function und bestimmen Sie anhand der Werte in der anfänglichen c-Funktion, welche c-Funktion aufgerufen werden soll. Python sollte also immer nur die one c-Funktion aufrufen. Diese eine c-Funktion betrachtet die Argumente und kann dann entsprechend an andere c-Funktionen delegieren.
Sie verwenden im Wesentlichen nur jede Unterklasse als einen anderen Datencontainer. Wenn Sie jedoch alle potenziellen Argumente für die Basisklasse definieren, können die Unterklassen diejenigen ignorieren, mit denen sie nichts tun.
Wenn ein neuer Aufzählungszeichentyp hinzukommt, können Sie einfach eine weitere Eigenschaft in der Basis definieren, die Python-Funktion so ändern, dass sie die zusätzliche Eigenschaft übergibt, und die eine c_-Funktion, die die Argumente und Delegaten entsprechend untersucht. Klingt nicht schlecht, denke ich.
quelle
pos+v*t
und dann mit Bildschirmgrenzen zu vergleichenif x > 800
und so weiter. Das mehrmalige Aufrufen dieser Funktionen pro Frame erwies sich als unannehmbar langsam. Es war ungefähr 40 fps bei 100% CPU mit reinem Python bis 60 fps mit 5% -10%, wenn es in C gemacht wurde.add_bullet
und extrahieren Sie alle Felder, die Sie benötigen. Ich werde meine Antwort bearbeiten.Durch Übergeben von Schlüsselwortargumenten .
quelle
Verwenden Sie entweder mehrere Schlüsselwortargumente in der Definition oder erstellen Sie eine
Bullet
Hierarchie, deren Instanzen an die Funktion übergeben werden.quelle
Ich denke, Ihre Grundvoraussetzung ist eine C / C ++ - ähnliche Syntax in Python mit möglichst wenig Kopfschmerzen. Obwohl mir die Antwort von Alexander Poluektov gefallen hat, funktioniert sie im Unterricht nicht.
Folgendes sollte für Klassen funktionieren. Es funktioniert durch Unterscheiden nach der Anzahl der Nicht-Schlüsselwortargumente (unterstützt jedoch nicht die Unterscheidung nach Typ):
Und es kann einfach so verwendet werden:
Ausgabe:
quelle
Der
@overload
Dekorateur wurde mit Typhinweisen (PEP 484) hinzugefügt. Dies ändert zwar nichts am Verhalten von Python, erleichtert jedoch das Verständnis der Vorgänge und das Erkennen von Fehlern durch mypy.Siehe: Tipphinweise und PEP 484
quelle
Ich denke, eine
Bullet
Klassenhierarchie mit dem damit verbundenen Polymorphismus ist der richtige Weg. Sie können den Basisklassenkonstruktor mithilfe einer Metaklasse effektiv überladen, sodass beim Aufrufen der Basisklasse das entsprechende Unterklassenobjekt erstellt wird. Im Folgenden finden Sie einen Beispielcode, der das Wesentliche meiner Bedeutung veranschaulicht.Aktualisiert
Der Code wurde so geändert, dass er sowohl unter Python 2 als auch unter Python 3 ausgeführt wird, damit er relevant bleibt. Dies wurde so durchgeführt, dass die explizite Metaklassensyntax von Python vermieden wird, die zwischen den beiden Versionen variiert.
Um dieses Ziel zu erreichen, wird eine
BulletMetaBase
Instanz derBulletMeta
Klasse erstellt, indem die Metaklasse beim Erstellen derBullet
Basisklasse explizit aufgerufen wird (anstatt das__metaclass__=
Klassenattribut zu verwenden oder über einmetaclass
Schlüsselwortargument abhängig von der Python-Version).Ausgabe:
quelle
Bullet
Unterklassen unterstützt, ohne dass die Basisklasse oder die Factory-Funktion jedes Mal geändert werden muss, wenn Sie einen anderen Subtyp hinzufügen. (Wenn Sie C anstelle von C ++ verwenden, haben Sie wahrscheinlich keine Klassen.) Sie können auch eine intelligentere Metaklasse erstellen, die selbst herausfindet, welche Unterklasse basierend auf dem Typ und / oder der Nummer erstellt werden soll von übergebenen Argumenten (wie C ++ zur Unterstützung der Überladung).Python 3.8 hat functools.singledispatchmethod hinzugefügt
Ausgabe
Ausgabe:
quelle
Verwenden Sie Schlüsselwortargumente mit Standardwerten. Z.B
Im Fall einer geraden Kugel im Vergleich zu einer gekrümmten Kugel würde ich zwei Funktionen hinzufügen:
add_bullet_straight
undadd_bullet_curved
.quelle
Überladungsmethoden sind in Python schwierig. Es kann jedoch sinnvoll sein, das Diktat, die Liste oder die primitiven Variablen zu übergeben.
Ich habe etwas für meine Anwendungsfälle ausprobiert. Dies könnte hier helfen, die Leute zu verstehen, die die Methoden überladen.
Nehmen wir Ihr Beispiel:
Eine Klassenüberladungsmethode mit Aufruf der Methoden aus verschiedenen Klassen.
Übergeben Sie die Argumente der Remote-Klasse:
ODER
Daher wird die Behandlung für Listen-, Wörterbuch- oder primitive Variablen durch Überladen von Methoden erreicht.
Probieren Sie es für Ihre Codes aus.
quelle
Nur ein einfacher Dekorateur
Sie können es so verwenden
Ändern Sie es, um es an Ihren Anwendungsfall anzupassen.
self/this
Argumenttyps , welche aufgerufen werden soll . Die meisten Sprachen tun dies jedoch nur für dasthis
Argument. Der obige Dekorateur erweitert die Idee auf mehrere Parameter.Nehmen Sie zum Aufräumen eine statische Sprache an und definieren Sie die Funktionen
Beim statischen Versand (Überladen) wird zweimal "Nummer angerufen" angezeigt, da dies
x
als deklariert wurdeNumber
, und das ist alles, was das Überladen betrifft. Beim dynamischen Versand wird "Ganzzahl aufgerufen, Float aufgerufen" angezeigt, da dies die tatsächlichen Typenx
zum Zeitpunkt des Funktionsaufrufs sind.quelle
x
für den dynamischen Versand aufgerufen wurde und in welcher Reihenfolge beide Methoden für den statischen Versand aufgerufen wurden. Empfehlen Sie, dieprint('number called for Integer')