Gebundenes und ungebundenes Methodenobjekt in Python

Ich habe Code über gebundene und ungebundene Methoden ausprobiert. Wenn wir sie anrufen, denke ich, dass beide Objekte zurückgeben würden. Aber wenn ich id() um Informationen zu erhalten, gibt es etwas zurück, was ich nicht verstehe.

IDE: Eclipse

Plugin: Pydev

 Class C(object): def foo(self): pass cobj = C() print id(C.foo) #1 print id(cobj.foo) #2 a = C.foo b = cobj.foo print id(a) #3 print id(b) #4 

Und die Ausgabe ist …

 5671672 5671672 5671672 5669368 

Warum geben # 1 und # 2 die gleiche ID zurück? Sind das nicht verschiedene Objekte? Und wenn wir C.foo und conj.foo zwei Variablen zuweisen, geben # 3 und # 4 die unterschiedliche ID zurück.

Ich denke, # 3 und # 4 zeigen, dass sie nicht das gleiche Objekt sind, sondern # 1 und # 2 …

Was ist der Unterschied zwischen der ID der gebundenen Methode und einer ungebundenen Methode?

Immer wenn Sie eine Methode über instance.name (und in Python 2, class.name ) class.name , wird das Methodenobjekt a-new erstellt. Python verwendet das Deskriptorprotokoll , um die function jedes Mal in ein Methodenobjekt zu schreiben.

Wenn Sie also nach id(C.foo) suchen, wird ein neues Methodenobjekt erstellt, Sie id(C.foo) seine ID (eine Speicheradresse) ab und vercasting das Methodenobjekt erneut . Dann suchen Sie id(cobj.foo) , ein neues Methodenobjekt, das die jetzt freigegebene Speicheradresse wieder verwendet und Sie sehen den gleichen Wert. Die Methode wird dann erneut verworfen (der Müll wird gesammelt, wenn der Referenzzähler auf 0 fällt).

Als Nächstes haben Sie einen Verweis auf die ungebundene C.foo Methode in einer Variablen gespeichert. Jetzt ist die Speicheradresse nicht freigegeben (der Referenzzählwert ist 1 anstelle von 0), und Sie erstellen eine zweite Methodeninstanz, indem Sie cobj.foo die einen neuen Speicherort verwenden muss. Somit erhalten Sie zwei verschiedene Werte.

Siehe die Dokumentation für id() :

Gib die “Identität” eines Objekts zurück. Dies ist eine Ganzzahl (oder Long Integer), die für dieses Objekt während seiner Lebensdauer garantiert eindeutig und konstant ist. Zwei Objekte mit nicht überlappenden Lebensdauern können denselben id() -Wert haben .

CPython-Implementierungsdetail : Dies ist die Adresse des Objekts im Speicher.

Schwerpunkt meiner.

Sie können eine Methode mit einem direkten Verweis auf die function über das Attribut __dict__ der class erneut erstellen und dann die Deskriptormethode __get__ aufrufen:

 >>> class C(object): ... def foo(self): ... pass ... >>> C.foo  >>> C.__dict__['foo']  >>> C.__dict__['foo'].__get__(None, C)  >>> C.__dict__['foo'].__get__(C(), C) > 

Beachten Sie, dass in Python 3 die gesamte Unterscheidung nach nicht gebundenen / gebundenen Methoden gelöscht wurde. Sie erhalten eine function, bei der Sie zuvor eine ungebundene Methode und eine Methode, bei der eine Methode immer gebunden ist, erhalten:

 >>> C.foo  >>> C.foo.__get__(None, C)  >>> C.foo.__get__(C(), C) > 

Außerdem fügt Python 3.7 ein neues Opcode-Paar LOAD_METHODCALL_METHOD hinzu, das das aktuelle Opcode-Paar LOAD_ATTRIBUTECALL_FUNCTION genau ersetzt, um zu vermeiden, dass jedes Mal ein neues Methodenobjekt erstellt wird. Diese Optimierung transformiert den Ausführungspfad für instance.foo() von type(instance).__dict__['foo'].__get__(instance, type(instance))() mit type(instance).__dict__['foo'](instance) , also ‘manuell’ die Instanz direkt an das functionsobjekt übergeben.

Hinzufügen zu @Martijn Pieters ‘sehr gute Antwort :

 In [1]: class C(object): ...: def foo(self): ...: pass ...: In [2]: c = C() In [3]: id(c.foo), id(C.foo) Out[3]: (149751844, 149751844) # so 149751844 is current free memory address In [4]: a = c.foo # now 149751844 is assigned to a In [5]: id(a) Out[5]: 149751844 # now python will allocate some different address to c.foo and C.foo In [6]: id(c.foo), id(C.foo) # different address used this time, and Out[6]: (149752284, 149752284) # that address is freed after this step # now 149752284 is again free, as it was not allocated to any variable In [7]: b = C.foo # now 149752284 is allocated to b In [8]: id(b) Out[8]: 149752284 In [9]: c.foo is C.foo # better use `is` to compare objects, rather than id() Out[9]: False