Beginnen wir mit dem, was wir bereits wissen: self – der erste Parameter in der Methode – bezieht sich auf die Klasseninstanz:
class MyClass: ┌─────────────────┐ ▼ │ def do_stuff(self, some_arg): │ print(some_arg)▲│ ││ ││ ││ ││ instance = MyClass() ││ instance.do_stuff("whatever") │ │ │ └───────────────────────────────┘
Außerdem muss dieses Argument nicht unbedingt self heißen – es ist einfach so eine Vereinbarung. Sie können es beispielsweise so verwenden, wie es in anderen Sprachen üblich ist.
Der obige Code mag natürlich und offensichtlich sein, weil Sie ihn verwendet haben, aber wir haben .do_stuff() nur einen Parameter (some_arg) gegeben, aber die Methode hat zwei deklariert (self und, some_arg), was auch zu sagen scheint: „Doesn'“ Das ergibt keinen Sinn. Der Pfeil im Snippet zeigt, dass self in eine Instanz übersetzt wird, aber wie wird es tatsächlich übergeben?
instance = MyClass() MyClass.do_stuff(instance, "whatever")
Was Python intern tut, ist die Konvertierung von „instance.do_stuff(„whatever“)“ in „MyClass.do_stuff(instance, „whatever“)“. Wir könnten es hier „Python-Magie“ nennen, aber wenn wir wirklich verstehen wollen, was sich hinter den Kulissen abspielt, müssen wir verstehen, was Python-Methoden sind und wie sie sich auf Funktionen beziehen.
In Python gibt es kein „Methoden“-Objekt – tatsächlich sind Methoden nur reguläre Funktionen. Der Unterschied zwischen Funktionen und Methoden besteht darin, dass Methoden im Namensraum einer Klasse definiert werden, wodurch sie zu Eigenschaften dieser Klasse werden.
Diese Eigenschaften werden im Klassenwörterbuch __dict__ gespeichert und wir können direkt darauf zugreifen oder die integrierte Vars-Funktion verwenden:
MyClass.__dict__["do_stuff"] # <function MyClass.do_stuff at 0x7f132b73d550> vars(MyClass)["do_stuff"] # <function MyClass.do_stuff at 0x7f132b73d550>
Der gebräuchlichste Weg, auf sie zuzugreifen, ist die „Klassenmethode“-Methode:
print(MyClass.do_stuff) # <function MyClass.do_stuff at 0x7f132b73d550>
Hier wird beim Zugriff auf die Funktion über das Klassenattribut wie erwartet ausgegeben, dass do_stuff eine Funktion von MyClass ist. Wir können jedoch auch über Instanzeigenschaften darauf zugreifen:
print(instance.do_stuff) # <bound method MyClass.do_stuff of <__main__.MyClass object at 0x7ff80c78de50>
In diesem Fall erhalten wir jedoch eine „gebundene Methode“ anstelle der ursprünglichen Funktion. Was Python hier für uns tut, ist, dass es Klassenattribute an Instanzen bindet und so sogenannte „gebundene Methoden“ erstellt. Diese „gebundene Methode“ ist ein Wrapper um die zugrunde liegende Funktion, der die Instanz bereits als erstes Argument (self) einfügt.
Methoden sind also gewöhnliche Funktionen, an deren anderen Parameter eine Klasseninstanz (self) angehängt ist.
Um zu verstehen, wie das passiert, müssen wir einen Blick auf das Deskriptorprotokoll werfen.
Deskriptoren sind der Mechanismus hinter Methoden. Sie sind Objekte (Klassen), die die Methoden __get__(), __set__() oder __delete__() definieren. Um zu verstehen, wie self funktioniert, betrachten wir einfach __get__(), das eine Signatur hat:
descr.__get__(self, instance, type=None) -> value
Aber was macht die Methode __get__() eigentlich? Es ermöglicht uns, die Eigenschaftssuche in einer Klasse anzupassen – oder mit anderen Worten: anzupassen, was passiert, wenn auf Klasseneigenschaften mithilfe der Punktnotation zugegriffen wird. Dies ist sehr nützlich, wenn man bedenkt, dass Methoden eigentlich nur Eigenschaften der Klasse sind. Das bedeutet, dass wir die Methode __get__ verwenden können, um eine „gebundene Methode“ einer Klasse zu erstellen.
Um das Verständnis zu erleichtern, demonstrieren wir dies, indem wir eine „Methode“ mithilfe von Deskriptoren implementieren. Zuerst erstellen wir eine reine Python-Implementierung eines Funktionsobjekts:
import types class Function: def __get__(self, instance, objtype=None): if instance is None: return self return types.MethodType(self, instance) def __call__(self): return
Die obige Function-Klasse implementiert __get__ , was sie zu einem Deskriptor macht. Diese spezielle Methode empfängt die Klasseninstanz im Instanzparameter – wenn dieser Parameter None ist, wissen wir, dass die __get__-Methode direkt von einer Klasse (z. B. MyClass.do_stuff) aufgerufen wurde, also geben wir einfach self zurück. Wenn es jedoch von einer Klasseninstanz wie „instance.do_stuff“ aufgerufen wird, geben wir „types.MethodType“ zurück, was eine Möglichkeit zum manuellen Erstellen einer „gebundenen Methode“ darstellt.
Darüber hinaus bieten wir auch die spezielle Methode __call__ an. __init__ wird aufgerufen, wenn eine Klasse aufgerufen wird, um eine Instanz zu initialisieren (z. B. Instanz = MyClass()), während __call__ aufgerufen wird, wenn eine Instanz aufgerufen wird (z. B. Instanz()). Wir müssen dies verwenden, da self intypes.MethodType(self,instance) aufrufbar sein muss.
Da wir nun unsere eigene Funktionsimplementierung haben, können wir diese verwenden, um Methoden an die Klasse zu binden:
class MyClass: do_stuff = Function() print(MyClass.__dict__["do_stuff"])# __get__ not invoked # <__main__.Function object at 0x7f229b046e50> print(MyClass.do_stuff)# __get__ invoked, but "instance" is None, "self" is returned print(MyClass.do_stuff.__get__(None, MyClass)) # <__main__.Function object at 0x7f229b046e50> instance = MyClass() print(instance.do_stuff)#__get__ invoked and "instance" is not None, "MethodType" is returned print(instance.do_stuff.__get__(instance, MyClass)) # <bound method ? of <__main__.MyClass object at 0x7fd526a33d30>
Indem wir MyClass ein Attribut do_stuff vom Typ Function geben, simulieren wir grob Pythons Definition von Methoden im Namensraum der Klasse Dinge, die damals gemacht wurden.
Zusammenfassend lässt sich sagen, dass beim Zugriff auf Attribute wie „instance.do_stuff“ nach „do_stuff“ im Attributwörterbuch (__dict__) der Instanz gesucht wird. Wenn do_stuff eine __get__-Methode definiert, wird do_stuff.__get__ aufgerufen und ruft letztendlich Folgendes auf:
# For class invocation: print(MyClass.__dict__['do_stuff'].__get__(None, MyClass)) # <__main__.Function object at 0x7f229b046e50> # For instance invocation: print(MyClass.__dict__['do_stuff'].__get__(instance, MyClass)) # Alternatively: print(type(instance).__dict__['do_stuff'].__get__(instance, type(instance))) # <bound method ? of <__main__.MyClass object at 0x7fd526a33d30>
Wie wir jetzt wissen, wird eine gebundene Methode zurückgegeben – ein aufrufbarer Wrapper um die ursprüngliche Funktion, dessen Argumenten self vorangestellt ist!
Wenn Sie dies weiter untersuchen möchten, können Sie statische und Klassenmethoden auf ähnliche Weise implementieren (https://docs.python.org/3.7/howto/descriptor.html#static-methods-and-class-methods)
Wir wissen jetzt, wie es funktioniert, aber es gibt eine philosophischere Frage: „Warum muss es in der Methodendefinition vorhanden sein?“
Der explizite Methodenparameter self ist eine umstrittene Designentscheidung, aber eine A-Wahl das begünstigt die Einfachheit.
Python verkörpert selbst die Designphilosophie „Schlimmer ist besser“ – hier beschrieben. Die Priorität dieser Designphilosophie ist „Einfachheit“, definiert als:
Das Design muss einfach sein, einschließlich Implementierung und Schnittstellen. Wichtiger ist, dass die Implementierung einfach ist als die Schnittstelle...
Genau das ist bei self der Fall – eine einfache Implementierung auf Kosten der Schnittstelle, bei der die Methodensignatur nicht mit ihrem Aufruf übereinstimmt.
Es gibt natürlich noch weitere Gründe, warum wir self explizit schreiben sollten oder warum es erhalten bleiben muss, von denen einige in einem Blogbeitrag von Guido van Rossum beschrieben werden (http://neopythonic.blogspot.com/2008/10/ why-explicit-self-has-to-stay.html) reagierte der Artikel auf einen Antrag auf Entfernung.
Python abstrahiert viel Komplexität, aber meiner Meinung nach ist das Eintauchen in die Details und die Komplexität auf niedriger Ebene äußerst wertvoll, um besser zu verstehen, wie die Sprache funktioniert, wenn etwas kaputt geht und eine erweiterte Fehlerbehebung/Debugging nicht sinnvoll genug ist.
Außerdem kann das Verständnis von Deskriptoren tatsächlich sehr praktisch sein, da sie einige Anwendungsfälle haben. Während Sie in den meisten Fällen nur @property-Deskriptoren benötigen, gibt es einige Fälle, in denen benutzerdefinierte Deskriptoren sinnvoll sind, z. B. die in SLQAlchemy oder z. B. benutzerdefinierte Validatoren.
Das obige ist der detaillierte Inhalt vonWas ist der self-Parameter in Python?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!