Klassenmethodenunterschiede in Python: gebunden, ungebunden und statisch

242

Was ist der Unterschied zwischen den folgenden Klassenmethoden?

Ist es so, dass einer statisch ist und der 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()
Franck Mesirard
quelle
18
Kein anderer Unterschied als die Definition von method_two () ist ungültig und der Aufruf schlägt fehl.
Anatoly Techtonik
14
@techtonik: An der Definition der Methode_two ist nichts falsch! Es wird in einer falschen / ungültigen Spezifikation aufgerufen, dh mit einem zusätzlichen Argument.
0xc0de
1
Ihre sind beide Instanzmethoden , keine Klassenmethoden. Sie erstellen eine Klassenmethode, indem Sie sie @classmethodauf die Definition anwenden . Der erste Parameter sollte clsanstelle von aufgerufen werden selfund empfängt das Klassenobjekt anstelle einer Instanz Ihrer Klasse: Test.method_three()und a_test.method_three()ist äquivalent.
Lutz Prechelt
Warum sollten Sie eine Funktionsdefinition ohne das selfArgument erstellen ? Gibt es dafür einen starken Anwendungsfall?
Alpha_989

Antworten:

412

In Python wird zwischen gebundenen und ungebundenen Methoden unterschieden.

Grundsätzlich ein Aufruf einer Mitgliedsfunktion (wie method_one), einer gebundenen Funktion

a_test.method_one()

wird übersetzt in

Test.method_one(a_test)

dh ein Aufruf einer ungebundenen Methode. Aus diesem Grund schlägt ein Aufruf Ihrer Version von method_twomit a fehlTypeError

>>> 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 Dekorateurs ändern

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

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

Der Dekorateur weist die integrierte Standard-Metaklasse type(die Klasse einer Klasse, siehe diese Frage ) an, keine gebundenen Methoden für zu erstellen method_two.

Jetzt können Sie die statische Methode sowohl für eine Instanz als auch für die 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
Torsten Marek
quelle
17
Ich unterstütze diese Antwort, sie ist meiner überlegen. Gut gemacht Torsten :)
Freespace
24
In Python 3 sind ungebundene Methoden veraltet. Stattdessen gibt es nur eine Funktion.
Boldnik
@boldnik, warum sagen Sie, dass ungebundene Methoden veraltet sind? statische Methoden sind noch in der Dokumentation vorhanden: docs.python.org/3/library/functions.html#staticmethod
alpha_989
195

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 nun diese Klasse in der Shell an:

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

Wie Sie sehen können, erhalten Sie beim Zugriff auf das fooAttribut der Klasse eine ungebundene Methode zurück. Im Klassenspeicher (das Diktat) befindet sich jedoch eine Funktion. Warum ist das? Der Grund dafür ist, dass die Klasse Ihrer Klasse ein implementiert __getattribute__, das Deskriptoren auflöst. Klingt komplex, ist es aber nicht. C.fooentspricht in diesem speziellen Fall in etwa 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 dieselbe, nur das Noneist die Klasseninstanz:

>>> 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. Manchmal möchten Sie nicht, dass Ihre Klasse eine Funktion zu einer Methode macht. Hier staticmethodkommt das ins Spiel:

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

Der staticmethodDekorateur umschließt Ihre Klasse und implementiert einen Dummy __get__, der 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.

Armin Ronacher
quelle
12
Der staticmethodDekorateur wickelt Ihre Klasse (...) Dieser Satz ist ein bisschen wie die Klasse irreführend , die gewickelt wird, die Klasse ist von der Methode foound nicht die Klasse , in der foodefiniert ist.
Piotr Dobrogost
12

Wenn Sie ein Klassenmitglied aufrufen, verwendet Python automatisch einen Verweis auf das Objekt als ersten Parameter. Die Variable selfbedeutet eigentlich nichts, es ist nur eine Codierungskonvention. Sie könnten es nennen, gargaloowenn Sie wollten. Der Aufruf von method_twowürde jedoch a auslösen TypeError, da Python automatisch versucht, einen Parameter (den Verweis auf sein übergeordnetes Objekt) an eine Methode zu übergeben, die als ohne Parameter 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 @staticmethod Funktionsdekorator verwenden .

Justin Poliey
quelle
4
Sie meinen "die Syntax des @staticmethod-Funktionsdekorators".
Zot
11
>>> 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
kzh
quelle
2
Class.instance_method() # The class cannot use an instance methodes kann verwenden. Class.instance_method(a)
Übergeben Sie
@warwaruk Es ist da, schau dir die Linie unter der TyeErrorLinie an.
kzh
Ja, ich habe es später gesehen. Trotzdem ist es nicht richtig zu sagen, dass die Klasse keine Instanzmethode verwenden kann, weil Sie es nur eine Zeile weiter unten getan haben.
Warvariuc
@kzh, Danke für deine Erklärung. Wenn Sie aufgerufen haben a.class_method(), scheint es a.caktualisiert worden zu sein 1, also hat der Aufruf von Class.class_method()die Class.cVariable auf aktualisiert 2. a.c=5Warum wurde bei der Zuweisung jedoch Class.cnicht auf aktualisiert 5?
Alpha_989
@ alpha_989 python sucht zuerst nach einem Attribut direkt in einer ovjects-eigenen Instanz. Wenn es nicht vorhanden ist, sucht es standardmäßig in seiner Klasse. Wenn Sie weitere Fragen dazu haben, können Sie diese gerne öffnen und hier verlinken. Ich helfe Ihnen gerne weiter.
kzh
4

method_two funktioniert nicht, weil Sie eine Mitgliedsfunktion definieren, aber nicht mitteilen, zu welcher Funktion 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 Elementfunktionen für eine Klasse definieren, muss das erste Argument immer 'self' sein.

Jon Cage
quelle
3

Genaue Erklärung von Armin Ronacher oben, wobei er seine Antworten erweitert, damit Anfänger wie ich es gut verstehen:

Der Unterschied in den in einer Klasse definierten Methoden, ob statische oder Instanzmethode (es gibt noch einen anderen Typ - Klassenmethode - hier nicht besprochen, also überspringen), lag in der Tatsache, ob sie irgendwie an die Klasseninstanz gebunden sind oder nicht. Angenommen, die Methode erhält zur Laufzeit einen Verweis auf die Klasseninstanz

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 damit

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

Die Methode foo ist wie oben beschrieben zugänglich. Ein wichtiger Punkt, der hier zu beachten ist, ist, dass alles in Python ein Objekt ist und daher Verweise im obigen Wörterbuch selbst auf andere Objekte verweisen. Lassen Sie mich sie Class Property Objects nennen - oder als CPO im Rahmen meiner Antwort der Kürze halber.

Wenn ein CPO ein Deskriptor ist, ruft der Python-Interpreter 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. Das Deskriptorprotokoll zu implementieren bedeutet, 3 Methoden zu implementieren

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

für zB

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

wo

  • self ist der CPO (es kann sich um eine Instanz von list, str, function usw. handeln) und wird von der Laufzeit bereitgestellt
  • instance ist die Instanz der Klasse, in der dieser CPO definiert ist (das Objekt 'c' oben) und muss von uns explizit angegeben werden
  • ownerist die Klasse, in der dieser CPO definiert ist (das Klassenobjekt 'C' oben) und von uns bereitgestellt werden muss. Dies liegt jedoch daran, dass wir es auf dem CPO aufrufen. Wenn wir es für die 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. Beispielsweise

>>> 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 seine Methodensignatur tatsächlich dies ist C.__dict__['foo'].__get__(c, C)(wie oben erläutert, wird C nicht benötigt, da es herausgefunden oder polymorphisiert werden kann). Aus diesem Grund erhalten Sie auch einen TypeError, wenn Sie das erforderliche Instanzargument nicht übergeben.

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

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, um den Deskriptor-CPO zu verpacken und seine __get__()Methode zu überschreiben, sodass kein Kontext erforderlich ist. Diese neue Klasse wird als Dekorateur bezeichnet und über das Schlüsselwort angewendet@staticmethod

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

Das Fehlen eines Kontexts im neu verpackten CPO foolöst 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 eine Namespace- und Code-Wartbarkeitsmethode (Herausnehmen aus einer Klasse und Bereitstellen im gesamten Modul usw.).

Es ist möglicherweise besser, statische Methoden als Instanzmethoden zu schreiben, wenn dies möglich ist, es sei denn, Sie müssen die Methoden (wie Zugriffsinstanzvariablen, Klassenvariablen usw.) kontexualisieren. Ein Grund ist, die Speicherbereinigung zu vereinfachen, indem keine unerwünschten Verweise auf Objekte beibehalten werden.

supi
quelle
1

das ist ein fehler

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

class Test(object):

Wenn Sie eine Methode einer Klasse aufrufen, wird sie als erstes Argument (daher der Name self) angezeigt, 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)
Hayalci
quelle
1

Das zweite funktioniert nicht, denn wenn Sie es so aufrufen, versucht Python intern, es mit der a_test-Instanz als erstem Argument aufzurufen, aber Ihre method_two akzeptiert keine Argumente, sodass es nicht funktioniert. Sie erhalten eine Laufzeit Error. Wenn Sie das Äquivalent einer statischen Methode wünschen, können Sie eine Klassenmethode verwenden. In Python werden Klassenmethoden viel weniger benötigt als statische Methoden in Sprachen wie Java oder C #. Meistens besteht die beste Lösung darin, eine Methode im Modul außerhalb einer Klassendefinition zu verwenden. Diese arbeiten effizienter als Klassenmethoden.

Vasil
quelle
Wenn ich eine Funktion definiere Class Test(object): @staticmethod def method_two(): print(“called method_two”) Ein Anwendungsfall, an den ich gedacht habe, ist, wenn Sie möchten, dass die Funktion Teil einer Klasse ist, der Benutzer jedoch nicht direkt auf die Funktion zugreifen soll. Das method_twokann also von anderen Funktionen innerhalb der TestInstanz aufgerufen werden, kann aber nicht mit verwendet werden a_test.method_two(). Wenn ich verwende def method_two(), funktioniert dies für diesen Anwendungsfall? Oder gibt es eine bessere Möglichkeit, die Funktionsdefinition so zu ändern, dass sie für den oben genannten Anwendungsfall wie vorgesehen funktioniert?
Alpha_989
1

Der Aufruf von method_two löst eine Ausnahme aus, wenn der self-Parameter nicht akzeptiert wird. Die Python-Laufzeit übergibt ihn automatisch.

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

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

Test.method_two()
MvdD
quelle
1

Bitte lesen Sie diese Dokumente aus der Guido First Class alles klar erklärt, wie ungebundene, gebundene Methoden geboren werden.

James Sapam
quelle
0

Die Definition von method_twoist ungültig. Wenn Sie anrufen method_two, erhalten Sie TypeError: method_two() takes 0 positional arguments but 1 was givenvom Dolmetscher.

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

Yossarian42
quelle
0

Ungebundene Methoden

Ungebundene Methoden sind Methoden, die noch nicht an eine bestimmte Klasseninstanz gebunden sind.

Gebundene Methoden

Gebundene Methoden sind diejenigen, die an eine bestimmte Instanz einer Klasse gebunden sind.

Wie hier dokumentiert , kann sich self je nach gebundener, ungebundener oder statischer Funktion auf verschiedene Dinge beziehen.

Schauen Sie sich das folgende Beispiel an:

class MyClass:    
    def some_method(self):
        return self  # For the sake of the example

>>> MyClass().some_method()
<__main__.MyClass object at 0x10e8e43a0># This can also be written as:>>> obj = MyClass()

>>> obj.some_method()
<__main__.MyClass object at 0x10ea12bb0>

# Bound method call:
>>> obj.some_method(10)
TypeError: some_method() takes 1 positional argument but 2 were given

# WHY IT DIDN'T WORK?
# obj.some_method(10) bound call translated as
# MyClass.some_method(obj, 10) unbound method and it takes 2 
# arguments now instead of 1 

# ----- USING THE UNBOUND METHOD ------
>>> MyClass.some_method(10)
10

Da wir die Klasseninstanz nicht verwendet haben - obj - beim letzten Aufruf nicht verwendet haben, können wir sagen, dass sie wie eine statische Methode aussieht.

Wenn ja, was ist der Unterschied zwischen einem MyClass.some_method(10)Aufruf und einem Aufruf einer statischen Funktion, die mit a dekoriert ist?@staticmethod Dekorateur ?

Durch die Verwendung des Dekorators wird ausdrücklich klargestellt, dass die Methode verwendet wird, ohne zuvor eine Instanz dafür zu erstellen. Normalerweise würde man nicht erwarten, dass die Klassenmitgliedsmethoden ohne die Instanz verwendet werden, und der Zugriff darauf kann je nach Struktur der Methode mögliche Fehler verursachen.

Durch Hinzufügen des @staticmethodDekorateurs können wir auch über ein Objekt erreichbar sein.

class MyClass:    
    def some_method(self):
        return self    

    @staticmethod
    def some_static_method(number):
        return number

>>> MyClass.some_static_method(10)   # without an instance
10
>>> MyClass().some_static_method(10)   # Calling through an instance
10

Sie können das obige Beispiel nicht mit den Instanzmethoden ausführen. Sie können den ersten überleben (wie zuvor), aber der zweite wird in einen ungebundenen Aufruf übersetzt, MyClass.some_method(obj, 10)der a auslöst, TypeErrorda die Instanzmethode ein Argument akzeptiert und Sie unbeabsichtigt versucht haben, zwei zu übergeben.

Dann könnten Sie sagen: "Wenn ich statische Methoden sowohl über eine Instanz als auch über eine Klasse aufrufen kann MyClass.some_static_methodund MyClass().some_static_methoddieselben Methoden verwenden sollte." Ja!

alexa
quelle
0

Gebundene Methode = Instanzmethode

Ungebundene Methode = statische Methode.

Python-Neuling
quelle
Bitte fügen Sie Ihrer Antwort eine weitere Erklärung hinzu, damit andere daraus lernen können. Schauen Sie sich zum Beispiel die anderen Antworten auf diese Frage an
Nico Haase