Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 991f996

Browse files
authored
Merge pull request #9084 from anntzer/boundmethodproxy-like-weakmethod
Require calling a _BoundMethodProxy to get the underlying callable.
2 parents 4b28fdb + 5257c4f commit 991f996

File tree

2 files changed

+54
-125
lines changed

2 files changed

+54
-125
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
`CallbackRegistry` now stores callbacks using stdlib's `WeakMethod`\s
2+
`````````````````````````````````````````````````````````````````````
3+
4+
In particular, this implies that ``CallbackRegistry.callbacks[signal]`` is now
5+
a mapping of callback ids to `WeakMethod`\s (i.e., they need to be first called
6+
with no arguments to retrieve the method itself).

lib/matplotlib/cbook/__init__.py

Lines changed: 48 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
import traceback
2727
import types
2828
import warnings
29-
from weakref import ref, WeakKeyDictionary
29+
import weakref
30+
from weakref import WeakMethod
3031

3132
import numpy as np
3233

@@ -61,100 +62,26 @@ def unicode_safe(s):
6162
return s
6263

6364

64-
class _BoundMethodProxy(object):
65-
"""
66-
Our own proxy object which enables weak references to bound and unbound
67-
methods and arbitrary callables. Pulls information about the function,
68-
class, and instance out of a bound method. Stores a weak reference to the
69-
instance to support garbage collection.
65+
def _exception_printer(exc):
66+
traceback.print_exc()
7067

71-
@organization: IBM Corporation
72-
@copyright: Copyright (c) 2005, 2006 IBM Corporation
73-
@license: The BSD License
7468

75-
Minor bugfixes by Michael Droettboom
69+
class _StrongRef:
70+
"""
71+
Wrapper similar to a weakref, but keeping a strong reference to the object.
7672
"""
77-
def __init__(self, cb):
78-
self._hash = hash(cb)
79-
self._destroy_callbacks = []
80-
try:
81-
try:
82-
self.inst = ref(cb.__self__, self._destroy)
83-
except TypeError:
84-
self.inst = None
85-
self.func = cb.__func__
86-
self.klass = cb.__self__.__class__
87-
except AttributeError:
88-
self.inst = None
89-
self.func = cb
90-
self.klass = None
91-
92-
def add_destroy_callback(self, callback):
93-
self._destroy_callbacks.append(_BoundMethodProxy(callback))
94-
95-
def _destroy(self, wk):
96-
for callback in self._destroy_callbacks:
97-
try:
98-
callback(self)
99-
except ReferenceError:
100-
pass
101-
102-
def __getstate__(self):
103-
d = self.__dict__.copy()
104-
# de-weak reference inst
105-
inst = d['inst']
106-
if inst is not None:
107-
d['inst'] = inst()
108-
return d
109-
110-
def __setstate__(self, statedict):
111-
self.__dict__ = statedict
112-
inst = statedict['inst']
113-
# turn inst back into a weakref
114-
if inst is not None:
115-
self.inst = ref(inst)
116-
117-
def __call__(self, *args, **kwargs):
118-
"""
119-
Proxy for a call to the weak referenced object. Take
120-
arbitrary params to pass to the callable.
121-
122-
Raises `ReferenceError`: When the weak reference refers to
123-
a dead object
124-
"""
125-
if self.inst is not None and self.inst() is None:
126-
raise ReferenceError
127-
elif self.inst is not None:
128-
# build a new instance method with a strong reference to the
129-
# instance
13073

131-
mtd = types.MethodType(self.func, self.inst())
74+
def __init__(self, obj):
75+
self._obj = obj
13276

133-
else:
134-
# not a bound method, just return the func
135-
mtd = self.func
136-
# invoke the callable and return the result
137-
return mtd(*args, **kwargs)
77+
def __call__(self):
78+
return self._obj
13879

13980
def __eq__(self, other):
140-
"""
141-
Compare the held function and instance with that held by
142-
another proxy.
143-
"""
144-
try:
145-
if self.inst is None:
146-
return self.func == other.func and other.inst is None
147-
else:
148-
return self.func == other.func and self.inst() == other.inst()
149-
except Exception:
150-
return False
81+
return isinstance(other, _StrongRef) and self._obj == other._obj
15182

15283
def __hash__(self):
153-
return self._hash
154-
155-
156-
def _exception_printer(exc):
157-
traceback.print_exc()
84+
return hash(self._obj)
15885

15986

16087
class CallbackRegistry(object):
@@ -179,20 +106,13 @@ class CallbackRegistry(object):
179106
>>> callbacks.disconnect(id_eat)
180107
>>> callbacks.process('eat', 456) # nothing will be called
181108
182-
In practice, one should always disconnect all callbacks when they
183-
are no longer needed to avoid dangling references (and thus memory
184-
leaks). However, real code in matplotlib rarely does so, and due
185-
to its design, it is rather difficult to place this kind of code.
186-
To get around this, and prevent this class of memory leaks, we
187-
instead store weak references to bound methods only, so when the
188-
destination object needs to die, the CallbackRegistry won't keep
189-
it alive. The Python stdlib weakref module can not create weak
190-
references to bound methods directly, so we need to create a proxy
191-
object to handle weak references to bound methods (or regular free
192-
functions). This technique was shared by Peter Parente on his
193-
`"Mindtrove" blog
194-
<http://mindtrove.info/python-weak-references/>`_.
195-
109+
In practice, one should always disconnect all callbacks when they are
110+
no longer needed to avoid dangling references (and thus memory leaks).
111+
However, real code in Matplotlib rarely does so, and due to its design,
112+
it is rather difficult to place this kind of code. To get around this,
113+
and prevent this class of memory leaks, we instead store weak references
114+
to bound methods only, so when the destination object needs to die, the
115+
CallbackRegistry won't keep it alive.
196116
197117
Parameters
198118
----------
@@ -211,12 +131,17 @@ def handler(exc: Exception) -> None:
211131
212132
def h(exc):
213133
traceback.print_exc()
214-
215134
"""
135+
136+
# We maintain two mappings:
137+
# callbacks: signal -> {cid -> callback}
138+
# _func_cid_map: signal -> {callback -> cid}
139+
# (actually, callbacks are weakrefs to the actual callbacks).
140+
216141
def __init__(self, exception_handler=_exception_printer):
217142
self.exception_handler = exception_handler
218-
self.callbacks = dict()
219-
self._cid = 0
143+
self.callbacks = {}
144+
self._cid_gen = itertools.count()
220145
self._func_cid_map = {}
221146

222147
# In general, callbacks may not be pickled; thus, we simply recreate an
@@ -236,18 +161,17 @@ def __setstate__(self, state):
236161
def connect(self, s, func):
237162
"""Register *func* to be called when signal *s* is generated.
238163
"""
239-
self._func_cid_map.setdefault(s, WeakKeyDictionary())
240-
# Note proxy not needed in python 3.
241-
# TODO rewrite this when support for python2.x gets dropped.
242-
proxy = _BoundMethodProxy(func)
164+
self._func_cid_map.setdefault(s, {})
165+
try:
166+
proxy = WeakMethod(func, self._remove_proxy)
167+
except TypeError:
168+
proxy = _StrongRef(func)
243169
if proxy in self._func_cid_map[s]:
244170
return self._func_cid_map[s][proxy]
245171

246-
proxy.add_destroy_callback(self._remove_proxy)
247-
self._cid += 1
248-
cid = self._cid
172+
cid = next(self._cid_gen)
249173
self._func_cid_map[s][proxy] = cid
250-
self.callbacks.setdefault(s, dict())
174+
self.callbacks.setdefault(s, {})
251175
self.callbacks[s][cid] = proxy
252176
return cid
253177

@@ -257,7 +181,6 @@ def _remove_proxy(self, proxy):
257181
del self.callbacks[signal][proxies[proxy]]
258182
except KeyError:
259183
pass
260-
261184
if len(self.callbacks[signal]) == 0:
262185
del self.callbacks[signal]
263186
del self._func_cid_map[signal]
@@ -284,12 +207,11 @@ def process(self, s, *args, **kwargs):
284207
All of the functions registered to receive callbacks on *s* will be
285208
called with ``*args`` and ``**kwargs``.
286209
"""
287-
if s in self.callbacks:
288-
for cid, proxy in list(self.callbacks[s].items()):
210+
for cid, ref in list(self.callbacks.get(s, {}).items()):
211+
func = ref()
212+
if func is not None:
289213
try:
290-
proxy(*args, **kwargs)
291-
except ReferenceError:
292-
self._remove_proxy(proxy)
214+
func(*args, **kwargs)
293215
# this does not capture KeyboardInterrupt, SystemExit,
294216
# and GeneratorExit
295217
except Exception as exc:
@@ -978,10 +900,10 @@ class Grouper(object):
978900
979901
"""
980902
def __init__(self, init=()):
981-
self._mapping = {ref(x): [ref(x)] for x in init}
903+
self._mapping = {weakref.ref(x): [weakref.ref(x)] for x in init}
982904

983905
def __contains__(self, item):
984-
return ref(item) in self._mapping
906+
return weakref.ref(item) in self._mapping
985907

986908
def clean(self):
987909
"""Clean dead weak references from the dictionary."""
@@ -996,10 +918,10 @@ def join(self, a, *args):
996918
Join given arguments into the same set. Accepts one or more arguments.
997919
"""
998920
mapping = self._mapping
999-
set_a = mapping.setdefault(ref(a), [ref(a)])
921+
set_a = mapping.setdefault(weakref.ref(a), [weakref.ref(a)])
1000922

1001923
for arg in args:
1002-
set_b = mapping.get(ref(arg), [ref(arg)])
924+
set_b = mapping.get(weakref.ref(arg), [weakref.ref(arg)])
1003925
if set_b is not set_a:
1004926
if len(set_b) > len(set_a):
1005927
set_a, set_b = set_b, set_a
@@ -1012,13 +934,14 @@ def join(self, a, *args):
1012934
def joined(self, a, b):
1013935
"""Returns True if *a* and *b* are members of the same set."""
1014936
self.clean()
1015-
return self._mapping.get(ref(a), object()) is self._mapping.get(ref(b))
937+
return (self._mapping.get(weakref.ref(a), object())
938+
is self._mapping.get(weakref.ref(b)))
1016939

1017940
def remove(self, a):
1018941
self.clean()
1019-
set_a = self._mapping.pop(ref(a), None)
942+
set_a = self._mapping.pop(weakref.ref(a), None)
1020943
if set_a:
1021-
set_a.remove(ref(a))
944+
set_a.remove(weakref.ref(a))
1022945

1023946
def __iter__(self):
1024947
"""
@@ -1034,7 +957,7 @@ def __iter__(self):
1034957
def get_siblings(self, a):
1035958
"""Returns all of the items joined with *a*, including itself."""
1036959
self.clean()
1037-
siblings = self._mapping.get(ref(a), [ref(a)])
960+
siblings = self._mapping.get(weakref.ref(a), [weakref.ref(a)])
1038961
return [x() for x in siblings]
1039962

1040963

0 commit comments

Comments
 (0)