it-swarm.com.de

Klassenmethodenunterschiede in Python: gebunden, ungebunden und statisch

Was ist der Unterschied zwischen den folgenden Klassenmethoden?

Ist das eine statisch und das andere nicht?

class Test(object):
  def method_one(self):
    print "Called method_one"

  def method_two():
    print "Called method_two"

a_test = Test()
a_test.method_one()
a_test.method_two()
225
Franck Mesirard

In Python wird zwischen bound - und unbound -Methoden unterschieden. 

Grundsätzlich ist ein Aufruf an eine Member-Funktion (wie method_one) eine gebundene Funktion

a_test.method_one()

wird übersetzt in

Test.method_one(a_test)

ein Aufruf an eine ungebundene Methode. Aus diesem Grund schlägt der Aufruf Ihrer Version von method_two mit einer TypeError fehl.

>>> a_test = Test() 
>>> a_test.method_two()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given) 

Sie können das Verhalten einer Methode mithilfe eines Dekorators ändern

class Test(object):
    def method_one(self):
        print "Called method_one"

    @staticmethod
    def method_two():
        print "Called method two"

Der Dekorateur teilt der eingebauten Standard-Metaklasse type (der Klasse einer Klasse, vgl. diese Frage ) mit, keine gebundenen Methoden für method_two zu erstellen.

Jetzt können Sie statische Methoden sowohl in einer Instanz als auch in der Klasse direkt aufrufen:

>>> a_test = Test()
>>> a_test.method_one()
Called method_one
>>> a_test.method_two()
Called method_two
>>> Test.method_two()
Called method_two
399
Torsten Marek

Methoden in Python sind eine sehr, sehr einfache Sache, wenn Sie die Grundlagen des Deskriptorsystems verstanden haben. Stellen Sie sich folgende Klasse vor:

class C(object):
    def foo(self):
        pass

Schauen wir uns jetzt diese Klasse in der Shell an:

>>> C.foo
<unbound method C.foo>
>>> C.__dict__['foo']
<function foo at 0x17d05b0>

Wie Sie sehen, wenn Sie auf das Attribut foo der Klasse zugreifen, erhalten Sie eine ungebundene Methode zurück. Innerhalb des Klassenspeichers (des Diktats) gibt es jedoch eine Funktion. Warum ist das? Der Grund dafür ist, dass die Klasse Ihrer Klasse einen __getattribute__ implementiert, der Deskriptoren auflöst. Hört sich komplex an, ist es aber nicht. C.foo entspricht in diesem speziellen Fall ungefähr diesem Code:

>>> C.__dict__['foo'].__get__(None, C)
<unbound method C.foo>

Das liegt daran, dass Funktionen eine __get__-Methode haben, die sie zu Deskriptoren macht. Wenn Sie eine Instanz einer Klasse haben, ist sie fast gleich, nur dass None die Klasseninstanz ist:

>>> c = C()
>>> C.__dict__['foo'].__get__(c, C)
<bound method C.foo of <__main__.C object at 0x17bd4d0>>

Warum macht Python das jetzt? Weil das Methodenobjekt den ersten Parameter einer Funktion an die Instanz der Klasse bindet. Da kommt das Selbst her. Jetzt möchten Sie manchmal, dass Ihre Klasse eine Funktion nicht zu einer Methode macht. Hier kommt staticmethod ins Spiel:

 class C(object):
  @staticmethod
  def foo():
   pass

Der Dekorator staticmethod umschließt Ihre Klasse und implementiert ein Dummy __get__, das die umschlossene Funktion als Funktion und nicht als Methode zurückgibt:

>>> C.__dict__['foo'].__get__(None, C)
<function foo at 0x17d0c30>

Hoffe das erklärt es.

185
Armin Ronacher

Wenn Sie ein Klassenmitglied aufrufen, verwendet Python automatisch einen Verweis auf das Objekt als ersten Parameter. Die Variable self bedeutet eigentlich nichts, es ist nur eine Kodierungskonvention. Sie könnten es gargaloo nennen, wenn Sie wollten. Der Aufruf von method_two würde jedoch eine TypeError auslösen, da Python automatisch versucht, einen Parameter (den Verweis auf das übergeordnete Objekt) an eine Methode zu übergeben, die als nicht definiert definiert wurde.

Damit es tatsächlich funktioniert, können Sie dies an Ihre Klassendefinition anhängen:

method_two = staticmethod(method_two)

oder Sie könnten den @staticmethodFunktionsdekorator verwenden.

11
Justin Poliey
>>> class Class(object):
...     def __init__(self):
...         self.i = 0
...     def instance_method(self):
...         self.i += 1
...         print self.i
...     c = 0
...     @classmethod
...     def class_method(cls):
...         cls.c += 1
...         print cls.c
...     @staticmethod
...     def static_method(s):
...         s += 1
...         print s
... 
>>> a = Class()
>>> a.class_method()
1
>>> Class.class_method()    # The class shares this value across instances
2
>>> a.instance_method()
1
>>> Class.instance_method() # The class cannot use an instance method
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method instance_method() must be called with Class instance as first argument (got nothing instead)
>>> Class.instance_method(a)
2
>>> b = 0
>>> a.static_method(b)
1
>>> a.static_method(a.c) # Static method does not have direct access to 
>>>                      # class or instance properties.
3
>>> Class.c        # a.c above was passed by value and not by reference.
2
>>> a.c
2
>>> a.c = 5        # The connection between the instance
>>> Class.c        # and its class is weak as seen here.
2
>>> Class.class_method()
3
>>> a.c
5
11
kzh

method_two funktioniert nicht, weil Sie eine Memberfunktion definieren, aber nicht sagen, wozu die Funktion gehört. Wenn Sie die letzte Zeile ausführen, erhalten Sie:

>>> a_test.method_two()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given)

Wenn Sie Mitgliedsfunktionen für eine Klasse definieren, muss das erste Argument immer 'self' sein.

4
Jon Cage

Eine genaue Erklärung von Armin Ronacher oben, die seine Antworten so erweitert, dass Anfänger wie ich es gut verstehen:

Der Unterschied in den in einer Klasse definierten Methoden, ob statisch oder Instanzmethode (es gibt noch einen anderen Typ - Klassenmethode - hier nicht diskutiert, so dass sie übersprungen wird), liegt in der Tatsache, ob sie irgendwie an die Klasseninstanz gebunden sind oder nicht. Sagen Sie beispielsweise, ob die Methode zur Laufzeit einen Verweis auf die Klasseninstanz erhält

class C:
    a = [] 
    def foo(self):
        pass

C # this is the class object
C.a # is a list object (class property object)
C.foo # is a function object (class property object)
c = C() 
c # this is the class instance

Die __dict__ dictionary-Eigenschaft des Klassenobjekts enthält den Verweis auf alle Eigenschaften und Methoden eines Klassenobjekts und somit 

>>> C.__dict__['foo']
<function foo at 0x17d05b0>

die Methode foo ist wie oben zugänglich. Ein wichtiger Punkt hierbei ist, dass alles in Python ein Objekt ist und die Verweise im obigen Wörterbuch auf andere Objekte verweisen. Lassen Sie mich sie Klasseneigenschaftsobjekte nennen - oder als CPO im Rahmen meiner Antwort auf die Kürze.

Wenn ein CPO ein Deskriptor ist, ruft der Python-Interpretor die __get__()-Methode des CPO auf, um auf den darin enthaltenen Wert zuzugreifen.

Um festzustellen, ob ein CPO ein Deskriptor ist, prüft der Python-Interpretor, ob er das Deskriptorprotokoll implementiert. Um das Deskriptorprotokoll zu implementieren, müssen 3 Methoden implementiert werden

def __get__(self, instance, owner)
def __set__(self, instance, value)
def __delete__(self, instance)

für z.B. 

>>> C.__dict__['foo'].__get__(c, C)

woher 

  • self ist das CPO (dies kann eine Instanz von list, str, function usw. sein) und wird von der Laufzeitumgebung bereitgestellt
  • instance ist die Instanz der Klasse, in der dieses CPO definiert ist (das Objekt 'c' oben) und muss von uns eindeutig angegeben werden
  • owner ist die Klasse, in der dieses CPO definiert ist (das Klassenobjekt 'C' oben) und muss von uns bereitgestellt werden. Dies liegt jedoch daran, dass wir es im CPO nennen. Wenn wir es in der Instanz aufrufen, müssen wir dies nicht angeben, da die Laufzeit die Instanz oder ihre Klasse bereitstellen kann (Polymorphismus).
  • value ist der beabsichtigte Wert für den CPO und muss von uns geliefert werden

Nicht alle CPO sind Deskriptoren. Zum Beispiel 

>>> C.__dict__['foo'].__get__(None, C)
<function C.foo at 0x10a72f510> 
>>> C.__dict__['a'].__get__(None, C)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute '__get__'

Dies liegt daran, dass die Listenklasse das Deskriptorprotokoll nicht implementiert.

Daher ist das Argument self in c.foo(self) erforderlich, da die Methodensignatur tatsächlich diese C.__dict__['foo'].__get__(c, C) ist (wie oben erklärt, ist C nicht erforderlich, da es herausgefunden oder polymorph gemacht werden kann) Deshalb erhalten Sie auch einen TypeError, wenn Sie nicht bestehen das erforderliche Instanzargument.

Wenn Sie feststellen, dass die Methode weiterhin über die Klasse Object C referenziert wird, wird die Bindung mit der Klasseninstanz durch Übergeben eines Kontexts in Form des Instanzobjekts in diese Funktion erreicht. 

Dies ist ziemlich beeindruckend, denn wenn Sie keinen Kontext oder keine Bindung an die Instanz beibehalten möchten, müssen Sie lediglich eine Klasse schreiben, die das Deskriptor-CPO umgibt, und die __get__()-Methode überschreiben, um keinen Kontext zu erfordern. Diese neue Klasse nennen wir Dekorateur und werden über das Schlüsselwort @staticmethod angewendet.

class C(object):
  @staticmethod
  def foo():
   pass

Das Fehlen eines Kontexts im neuen umschlossenen CPO foo gibt keinen Fehler aus und kann wie folgt überprüft werden:

>>> C.__dict__['foo'].__get__(None, C)
<function foo at 0x17d0c30>

Der Anwendungsfall einer statischen Methode ist eher ein Namensraum und eine Code-Wartbarkeit (sie wird aus einer Klasse herausgenommen und im gesamten Modul usw. verfügbar gemacht). 

Es ist möglicherweise besser, statische Methoden statt Instanzmethoden zu schreiben, sofern dies nicht möglich ist, es sei denn, Sie müssen die Methoden (z. B. Zugriffsinstanzvariablen, Klassenvariablen usw.) kontextualisieren. Ein Grund besteht darin, die Speicherbereinigung zu vereinfachen, indem keine unerwünschten Verweise auf Objekte beibehalten werden.

3
supi

Der Aufruf von method_two löst eine Ausnahme aus, da der Self-Parameter nicht akzeptiert wird.

Wenn Sie eine statische Methode in einer Python-Klasse erstellen möchten, dekorieren Sie sie mit dem staticmethod decorator.

Class Test(Object):
  @staticmethod
  def method_two():
    print "Called method_two"

Test.method_two()
1
MvdD

das ist ein fehler.

vor allem sollte die erste Zeile so sein (Vorsicht bei Großbuchstaben)

class Test(object):

Wenn Sie eine Methode einer Klasse aufrufen, erhält sie sich selbst als erstes Argument (daher der Name self), und method_two gibt diesen Fehler aus 

>>> a.method_two()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given)
1
hayalci

Die zweite Funktion funktioniert nicht, da Python bei seinem Aufruf intern versucht, die Instanz mit der Instanz a_test als erstes Argument aufzurufen. Ihr method_two akzeptiert jedoch keine Argumente. Daher wird keine Laufzeit angezeigt error . Wenn Sie das Äquivalent einer statischen Methode erhalten möchten, können Sie eine Klassenmethode . In Python sind Klassenmethoden weniger erforderlich als statische Methoden in Sprachen wie Java oder C #. In den meisten Fällen ist die beste Lösung die Verwendung einer Methode im Modul außerhalb einer Klassendefinition, die effizienter arbeitet als Klassenmethoden.

1
Vasil

Bitte lesen Sie diese Dokumente aus der Guido Erste Klasse alles Klar erklärt, wie ungebundene, gebundene Methoden geboren werden.

1
James Sapam

Die Definition von method_two ist ungültig. Wenn Sie method_two aufrufen, erhalten Sie TypeError: method_two() takes 0 positional arguments but 1 was given vom Interpreter. 

Eine Instanzmethode ist eine beschränkte Funktion, wenn Sie sie wie a_test.method_two() aufrufen. Es akzeptiert automatisch self, das auf eine Instanz von Test verweist, als ersten Parameter. Über den Parameter self kann eine Instanzmethode frei auf Attribute zugreifen und sie für dasselbe Objekt ändern.

0
Yossarian42