2013-10-24 6 views
6

ich so etwas wie dies heute (nicht im Gegensatz zu der mpl_connect Dokumentation schrieb:Wenn ich einen Verweis auf eine gebundene Methode in Python habe, wird das das Objekt allein am Leben erhalten?

class Foo(object): 
    def __init__(self): print 'init Foo', self 
    def __del__(self): print 'del Foo', self 
    def callback(self, event=None): print 'Foo.callback', self, event 


from pylab import * 
fig = figure() 
plot(randn(10)) 
cid = fig.canvas.mpl_connect('button_press_event', Foo().callback) 
show() 

Das sieht vernünftig, aber es funktioniert nicht - es ist, als ob matplotlib Spur der Funktion verliert ich ihm gegeben habe If. ., statt sie zu vorbei Foo().callback ich es passieren lambda e: Foo().callback(e), es funktioniert ähnlich, wenn ich sage x = Foo() und es dann x.callback passieren, es funktioniert

Meine Vermutung ist, dass die unbenannte Foo Instanz von Foo() erstellt unmittelbar nach der mpl_connect Linie zerstört wird. - - dass Matplotlib diehatReferenz hält die Foo nicht am Leben. Ist das korrekt?

Im Nicht-Spielzeug Code, den ich dies in angetroffen hat die Lösung von x = Foo() nicht, weil vermutlich in diesem Fall show() war anderswo so hatte x den Gültigkeitsbereich gegangen.

Allgemeiner ist Foo().callback ein <bound method Foo.callback of <__main__.Foo object at 0x03B37890>>. Meine erste Überraschung ist, dass es so aussieht, als ob eine gebundene Methode nicht wirklich auf das Objekt verweist. Ist das korrekt?

+1

Wird "del Foo" jemals gedruckt? –

Antwort

4

Ja, eine gebundene Methode verweist auf das Objekt - das Objekt ist der Wert des Attributs .im_self eines gebundenen Methodenobjekts.

Also frage ich mich, ob matplotlib 's mpl_connect() erinnert sich, die Referenzzählungen auf die Argumente zu erhöhen. Wenn nicht (und das ist ein häufiger Fehler), dann gibt es nichts, das den anonymen Foo().callback am Leben erhält, wenn mpl_connect() zurückkehrt.

Wenn Sie einfachen Zugriff auf den Quellcode haben, werfen Sie einen Blick auf die mpl_connect() Implementierung? Sie wollen C-Code zu tun Py_INCREF() ;-)

EDIT Das sieht relevant, from docs here sehen:

Die Leinwand behält nur schwache Hinweise auf die Rückrufe. Daher Wenn ein Rückruf eine Methode einer Klasseninstanz ist, müssen Sie einen Verweis auf diese Instanz beibehalten. Andernfalls wird die Instanz Müll- gesammelt und der Rückruf wird verschwinden.

So ist es Ihre Fehler - LOL ;-)

+1

'mpl_connect' von' FigureCanvasBase' verwenden 'weakref.WeakKeyDictionary' und' weakref.ref'. [Related Source Code] (https://github.com/matplotlib/matplotlib/blob/63f13c8d16c2b53ec2380b67f5b6ab45f6193bf9/lib/matplotlib/cbook.py#L474) – falsetru

+0

@falsetru, yup, gerade bearbeitet, um zu zeigen, dass die Dokumente sogar so sagen. –

+0

Danke. Du hast recht. Ich bin zu den Beispielen gesprungen, die sie geben, aber wenn der Aufruf zum "Zeigen" außerhalb des Geltungsbereichs ist, keine Würfel. – Ben

1

Hier ist die Rechtfertigung von matplotlib.cbook.CallbackRegistry.__doc__:

In der Praxis sollte man immer alle Rückrufe trennen, wenn sie werden nicht mehr benötigt, um vermeiden Sie dangling Referenzen (und damit Speicher Lecks). Realer Code in Matplotlib tut dies jedoch selten, und aufgrund seines Designs ist es ziemlich schwierig, diese Art von Code zu platzieren. Um dies zu umgehen und diese Klasse von Speicherlecks zu verhindern, speichern wir stattdessen nur schwache Referenzen auf gebundene Methoden. Wenn das Zielobjekt also absterben muss, wird die CallbackRegistry nicht am Leben erhalten.Das Python-Modul "stdlib weakref" kann keine schwachen Verweise auf gebundene Methoden direkt erstellen. Daher müssen wir ein Proxyobjekt erstellen, um schwache Verweise auf gebundene Methoden (oder reguläre freie -Funktionen) zu handhaben. Diese Technik wurde von Peter Parente auf seiner "Mindtrove" blog <http://mindtrove.info/articles/python-weak-references/> _ geteilt.

Es ist eine Schande, dass es keine offizielle Möglichkeit gibt, dieses Verhalten zu umgehen.

Hier ist eine Flickschusterei, um es zu bekommen, die möglicherweise verschmutzt ist, aber für Nicht-Produktionstest/Diagnose-Code OK sein: von lambda e: Foo().callback(e) warum

fig._dont_forget_this = Foo() 
cid = fig.canvas.mpl_connect('button_press_event', fig._dont_forget_this.callback) 

Dies lässt noch die Frage: Bringen Sie die Foo die die Figur funktioniert. Es macht natürlich einen neuen Foo bei jedem Anruf, aber warum wird das Lambda nicht Müll gesammelt? Ist die Tatsache, dass es funktioniert nur ein Fall von undefiniertem Verhalten?

+0

Sie würden zumindest denken, 'mpl_connect' könnte ein' strongref' Argument haben, also könnten Sie 'fig.canvas.mpl_connect ('button_press_event', strongref = Foo(). Callback)' sagen – Ben

Verwandte Themen