Hier ist was ich denke passiert:
p1.useless_func
dereferenzieren, wird eine Kopie davon im Speicher erstellt. Dieser Speicherort wird von id
zurückgegeben.
p2.useless_func
dereferenzieren, wird eine Kopie davon in derselben Speicheradresse erstellt (sie war verfügbar), die Sie mit id
erneut abrufen. Wenn Sie eine Menge anderen Code ausführen und die IDs der Instanzmethoden erneut überprüfen würden, wette ich, dass die id
s identisch sind, sich aber vom ursprünglichen Lauf unterscheiden.
Außerdem können Sie bemerken, dass in David Wolvers Beispiel eine dauerhafte Referenz auf die Methodenkopie erhalten wird, die id
s werden anders.
Um diese Theorie zu bestätigen, folgt hier eine Shell-Sitzung mit Jython (dasselbe Ergebnis mit PyPy), die CPythons Referenzzähler-Speicherbereinigung nicht verwendet:
%Vor%Das ist eine sehr interessante Frage!
Unter Ihren Bedingungen erscheinen sie gleich:
%Vor%Beachten Sie jedoch, dass wenn Sie etwas mit ihnen machen, sie anders werden:
%Vor%Und dann, wenn sie erneut getestet werden, haben sie sich wieder verändert!
%Vor%Wenn auf eine Methode für eine Instanz zugegriffen wird, wird eine Instanz der "gebundenen Methode" zurückgegeben. Eine gebundene Methode speichert einen Verweis auf die Instanz und auf das Funktionsobjekt der Methode:
%Vor% (Beachten Sie, dass ich Foo.__dict__['method']
, nicht Foo.method
verwenden muss, weil Foo.method
eine "ungebundene Methode" ergibt ... deren Zweck dem Leser als Übung überlassen wird)
Der Zweck dieses Objekts "gebundene Methode" besteht darin, Methoden "sich sinnvoll zu verhalten", wenn sie wie Funktionen weitergegeben werden. Wenn ich zum Beispiel die Funktion a_m()
aufruft, ist das identisch mit dem Aufruf von a.method()
, obwohl wir keinen expliziten Verweis auf a
mehr haben. Vergleichen Sie dieses Verhalten mit JavaScript (zum Beispiel), wobei var method = foo.method; method()
nicht dasselbe Ergebnis liefert wie foo.method()
.
SO! Dies bringt uns zurück zur ersten Frage: Warum scheint id(a.method)
den gleichen Wert wie id(b.method)
zu haben? Ich glaube, dass Asad richtig ist: es hat mit Python's zählendem Müllsammler zu tun *: wenn der Ausdruck id(a.method)
ausgewertet wird, wird eine gebundene Methode zugewiesen, die ID berechnet und die gebundene Methode wird freigegeben. Wenn die nächste gebundene Methode - für b.method
- zugewiesen wird, wird sie genau dem gleichen Speicherort im Speicher zugeordnet, da seit der gebundenen Methode für a.method
keine (oder eine ausgeglichene Anzahl) Zuweisungen stattgefunden haben. wurde zugeteilt. Dies bedeutet, dass a.method
den gleichen Speicherort hat wie b.method
.
Schließlich erklärt dies, warum sich die Speicherplätze bei der zweiten Überprüfung scheinbar ändern: Die anderen Zuordnungen, die zwischen der ersten und der zweiten Überprüfung stattgefunden haben, bedeuten, dass sie beim zweiten Mal an einem anderen Ort zugewiesen werden ( Hinweis: Sie werden neu zugewiesen, weil alle Verweise auf sie verloren gegangen sind, gebundene Methoden werden zwischengespeichert †, dh, wenn Sie zweimal auf dieselbe Methode zugreifen, wird dieselbe Instanz zurückgegeben: a_m0 = a.method; a_m1 = a.method; a_m0 is a_m1 => True
).
*: Anmerkung der Pedanten: Eigentlich hat das nichts mit dem eigentlichen Müllsammler zu tun, der nur existiert, um mit zirkulären Referenzen umzugehen ... aber ... das ist eine Geschichte für einen anderen Tag.
†: zumindest in CPython 2.7; CPython 2.6 scheint gebundene Methoden nicht im Cache zu speichern, was mich zu der Annahme verleiten würde, dass das Verhalten nicht spezifiziert ist.
Tags und Links python