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

Skip to content

Commit c1a749a

Browse files
committed
Merge pull request matplotlib#4118 from OceanWolf/callback-registry-fix
BUG : CallbackRegistry fix Correctly proxy instance methods.
2 parents e4222e8 + 03d97f2 commit c1a749a

File tree

2 files changed

+87
-11
lines changed

2 files changed

+87
-11
lines changed

lib/matplotlib/cbook.py

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -361,9 +361,14 @@ class _BoundMethodProxy(object):
361361
Minor bugfixes by Michael Droettboom
362362
'''
363363
def __init__(self, cb):
364+
self._hash = hash(cb)
365+
self._destroy_callbacks = []
364366
try:
365367
try:
366-
self.inst = ref(cb.im_self)
368+
if six.PY3:
369+
self.inst = ref(cb.__self__, self._destroy)
370+
else:
371+
self.inst = ref(cb.im_self, self._destroy)
367372
except TypeError:
368373
self.inst = None
369374
if six.PY3:
@@ -377,6 +382,16 @@ def __init__(self, cb):
377382
self.func = cb
378383
self.klass = None
379384

385+
def add_destroy_callback(self, callback):
386+
self._destroy_callbacks.append(_BoundMethodProxy(callback))
387+
388+
def _destroy(self, wk):
389+
for callback in self._destroy_callbacks:
390+
try:
391+
callback(self)
392+
except ReferenceError:
393+
pass
394+
380395
def __getstate__(self):
381396
d = self.__dict__.copy()
382397
# de-weak reference inst
@@ -433,6 +448,9 @@ def __ne__(self, other):
433448
'''
434449
return not self.__eq__(other)
435450

451+
def __hash__(self):
452+
return self._hash
453+
436454

437455
class CallbackRegistry(object):
438456
"""
@@ -492,17 +510,32 @@ def connect(self, s, func):
492510
func will be called
493511
"""
494512
self._func_cid_map.setdefault(s, WeakKeyDictionary())
495-
if func in self._func_cid_map[s]:
496-
return self._func_cid_map[s][func]
513+
# Note proxy not needed in python 3.
514+
# TODO rewrite this when support for python2.x gets dropped.
515+
proxy = _BoundMethodProxy(func)
516+
if proxy in self._func_cid_map[s]:
517+
return self._func_cid_map[s][proxy]
497518

519+
proxy.add_destroy_callback(self._remove_proxy)
498520
self._cid += 1
499521
cid = self._cid
500-
self._func_cid_map[s][func] = cid
522+
self._func_cid_map[s][proxy] = cid
501523
self.callbacks.setdefault(s, dict())
502-
proxy = _BoundMethodProxy(func)
503524
self.callbacks[s][cid] = proxy
504525
return cid
505526

527+
def _remove_proxy(self, proxy):
528+
for signal, proxies in list(six.iteritems(self._func_cid_map)):
529+
try:
530+
del self.callbacks[signal][proxies[proxy]]
531+
except KeyError:
532+
pass
533+
534+
if len(self.callbacks[signal]) == 0:
535+
del self.callbacks[signal]
536+
del self._func_cid_map[signal]
537+
538+
506539
def disconnect(self, cid):
507540
"""
508541
disconnect the callback registered with callback id *cid*
@@ -513,7 +546,7 @@ def disconnect(self, cid):
513546
except KeyError:
514547
continue
515548
else:
516-
for category, functions in list(
549+
for signal, functions in list(
517550
six.iteritems(self._func_cid_map)):
518551
for function, value in list(six.iteritems(functions)):
519552
if value == cid:
@@ -527,11 +560,10 @@ def process(self, s, *args, **kwargs):
527560
"""
528561
if s in self.callbacks:
529562
for cid, proxy in list(six.iteritems(self.callbacks[s])):
530-
# Clean out dead references
531-
if proxy.inst is not None and proxy.inst() is None:
532-
del self.callbacks[s][cid]
533-
else:
563+
try:
534564
proxy(*args, **kwargs)
565+
except ReferenceError:
566+
self._remove_proxy(proxy)
535567

536568

537569
class Scheduler(threading.Thread):

lib/matplotlib/tests/test_cbook.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import numpy as np
99
from numpy.testing.utils import (assert_array_equal, assert_approx_equal,
1010
assert_array_almost_equal)
11-
from nose.tools import assert_equal, raises, assert_true
11+
from nose.tools import assert_equal, assert_not_equal, raises, assert_true
1212

1313
import matplotlib.cbook as cbook
1414
import matplotlib.colors as mcolors
@@ -243,3 +243,47 @@ def test_label_error(self):
243243
def test_bad_dims(self):
244244
data = np.random.normal(size=(34, 34, 34))
245245
results = cbook.boxplot_stats(data)
246+
247+
248+
class Test_callback_registry(object):
249+
def setup(self):
250+
self.signal = 'test'
251+
self.callbacks = cbook.CallbackRegistry()
252+
253+
def connect(self, s, func):
254+
return self.callbacks.connect(s, func)
255+
256+
def is_empty(self):
257+
assert_equal(self.callbacks._func_cid_map, {})
258+
assert_equal(self.callbacks.callbacks, {})
259+
260+
def is_not_empty(self):
261+
assert_not_equal(self.callbacks._func_cid_map, {})
262+
assert_not_equal(self.callbacks.callbacks, {})
263+
264+
def test_callback_complete(self):
265+
# ensure we start with an empty registry
266+
self.is_empty()
267+
268+
# create a class for testing
269+
mini_me = Test_callback_registry()
270+
271+
# test that we can add a callback
272+
cid1 = self.connect(self.signal, mini_me.dummy)
273+
assert_equal(type(cid1), int)
274+
self.is_not_empty()
275+
276+
# test that we don't add a second callback
277+
cid2 = self.connect(self.signal, mini_me.dummy)
278+
assert_equal(cid1, cid2)
279+
self.is_not_empty()
280+
assert_equal(len(self.callbacks._func_cid_map), 1)
281+
assert_equal(len(self.callbacks.callbacks), 1)
282+
283+
del mini_me
284+
285+
# check we now have no callbacks registered
286+
self.is_empty()
287+
288+
def dummy(self):
289+
pass

0 commit comments

Comments
 (0)