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

Skip to content

Commit da7a5cc

Browse files
committed
simplify into single function with overload and api rename
1 parent 469e094 commit da7a5cc

4 files changed

Lines changed: 109 additions & 26 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
``CallbackRegistry.disconnect`` *cid* parameter renamed to *cid_or_func*
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
The *cid* parameter of `.CallbackRegistry.disconnect` has been renamed to
4+
*cid_or_func*. The method now also accepts a callable, which will disconnect
5+
that callback from all signals or from a specific signal if the *signal*
6+
keyword argument is provided.
7+
8+
``CallbackRegistry.disconnect_func`` has been removed; use
9+
``disconnect(func, signal=...)`` instead.

lib/matplotlib/cbook.py

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ class CallbackRegistry:
229229
>>> callbacks.process('drink', 123)
230230
drink 123
231231
232-
>>> callbacks.disconnect_func('drink', ondrink) # disconnect by func
232+
>>> callbacks.disconnect(ondrink, signal='drink') # disconnect by func
233233
>>> callbacks.process('drink', 123) # nothing will be called
234234
235235
In practice, one should always disconnect all callbacks when they are
@@ -334,27 +334,45 @@ def _remove_proxy(self, signal, proxy, *, _is_finalizing=sys.is_finalizing):
334334
if len(self.callbacks[signal]) == 0: # Clean up empty dicts
335335
del self.callbacks[signal]
336336

337-
def disconnect(self, cid):
337+
@_api.rename_parameter("3.11", "cid", "cid_or_func")
338+
def disconnect(self, cid_or_func, *, signal=None):
338339
"""
339-
Disconnect the callback registered with callback id *cid*.
340-
341-
No error is raised if such a callback does not exist.
342-
"""
343-
for signal, proxy in self._func_cid_map:
344-
if self._func_cid_map[signal, proxy] == cid:
345-
break
346-
else: # Not found
347-
return
348-
self._remove_proxy(signal, proxy)
349-
350-
def disconnect_func(self, signal, func):
351-
"""
352-
Disconnect the callback for *func* registered to *signal*.
340+
Disconnect a callback.
353341
342+
Parameters
343+
----------
344+
cid_or_func : int or callable
345+
If an int, disconnect the callback with that connection id.
346+
If a callable, disconnect that function from signals.
347+
signal : optional
348+
Only used when *cid_or_func* is a callable. If given, disconnect
349+
the function only from that specific signal. If not given,
350+
disconnect from all signals the function is connected to.
351+
352+
Notes
353+
-----
354354
No error is raised if such a callback does not exist.
355355
"""
356-
proxy = _weak_or_strong_ref(func, None)
357-
self._remove_proxy(signal, proxy)
356+
if isinstance(cid_or_func, int):
357+
if signal is not None:
358+
raise ValueError(
359+
"signal cannot be specified when disconnecting by cid")
360+
for sig, proxy in self._func_cid_map:
361+
if self._func_cid_map[sig, proxy] == cid_or_func:
362+
break
363+
else: # Not found
364+
return
365+
self._remove_proxy(sig, proxy)
366+
elif signal is not None:
367+
# Disconnect from a specific signal
368+
proxy = _weak_or_strong_ref(cid_or_func, None)
369+
self._remove_proxy(signal, proxy)
370+
else:
371+
# Disconnect from all signals
372+
proxy = _weak_or_strong_ref(cid_or_func, None)
373+
for sig, prx in list(self._func_cid_map):
374+
if prx == proxy:
375+
self._remove_proxy(sig, proxy)
358376

359377
def process(self, s, *args, **kwargs):
360378
"""

lib/matplotlib/cbook.pyi

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@ class CallbackRegistry:
3333
signals: Iterable[Any] | None = ...,
3434
) -> None: ...
3535
def connect(self, signal: Any, func: Callable) -> int: ...
36-
def disconnect(self, cid: int) -> None: ...
37-
def disconnect_func(self, signal: Any, func: Callable) -> None: ...
36+
@overload
37+
def disconnect(self, cid_or_func: int) -> None: ...
38+
@overload
39+
def disconnect(self, cid_or_func: Callable, *, signal: Any | None = ...) -> None: ...
3840
def process(self, s: Any, *args, **kwargs) -> None: ...
3941
def blocked(
4042
self, *, signal: Any | None = ...

lib/matplotlib/tests/test_cbook.py

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ def test_callback_disconnect_func(self, pickle, cls):
306306
self.is_not_empty()
307307

308308
# disconnect by function reference
309-
self.callbacks.disconnect_func(self.signal, mini_me.dummy)
309+
self.callbacks.disconnect(mini_me.dummy, signal=self.signal)
310310

311311
# check we now have no callbacks registered
312312
self.is_empty()
@@ -325,22 +325,22 @@ def test_callback_disconnect_func_wrong(self, pickle, cls):
325325
self.is_not_empty()
326326

327327
# try to disconnect with wrong signal - should do nothing
328-
self.callbacks.disconnect_func('wrong_signal', mini_me.dummy)
328+
self.callbacks.disconnect(mini_me.dummy, signal='wrong_signal')
329329

330330
# check we still have callbacks registered
331331
self.is_not_empty()
332332

333333
# try to disconnect with wrong function - should do nothing
334334
mini_me2 = cls()
335-
self.callbacks.disconnect_func(self.signal, mini_me2.dummy)
335+
self.callbacks.disconnect(mini_me2.dummy, signal=self.signal)
336336

337337
# check we still have callbacks registered
338338
self.is_not_empty()
339339

340340
def test_callback_disconnect_func_redefined(self):
341-
# Test that redefining a function name doesn't affect disconnect_func.
341+
# Test that redefining a function name doesn't affect disconnect.
342342
# When you redefine a function, it creates a new function object,
343-
# so disconnect_func should not disconnect the original.
343+
# so disconnect should not disconnect the original.
344344
self.is_empty()
345345

346346
def func():
@@ -354,11 +354,65 @@ def func():
354354
pass
355355

356356
# Try to disconnect with the redefined function
357-
self.callbacks.disconnect_func(self.signal, func)
357+
self.callbacks.disconnect(func, signal=self.signal)
358358

359359
# Original callback should still be registered
360360
self.is_not_empty()
361361

362+
@pytest.mark.parametrize('pickle', [True, False])
363+
@pytest.mark.parametrize('cls', [Hashable, Unhashable])
364+
def test_callback_disconnect_func_all_signals(self, pickle, cls):
365+
# Test disconnecting a callback from all signals at once
366+
self.is_empty()
367+
368+
mini_me = cls()
369+
370+
# Connect to multiple signals
371+
self.callbacks.connect('signal1', mini_me.dummy)
372+
self.callbacks.connect('signal2', mini_me.dummy)
373+
assert len(list(self.callbacks._func_cid_map)) == 2
374+
375+
# Disconnect from all signals at once (no signal specified)
376+
self.callbacks.disconnect(mini_me.dummy)
377+
378+
# All callbacks should be removed
379+
self.is_empty()
380+
381+
def test_disconnect_cid_with_signal_raises(self):
382+
# Passing signal with a cid should raise an error
383+
self.is_empty()
384+
cid = self.callbacks.connect(self.signal, lambda: None)
385+
with pytest.raises(ValueError, match="signal cannot be specified"):
386+
self.callbacks.disconnect(cid, signal=self.signal)
387+
388+
@pytest.mark.parametrize('pickle', [True, False])
389+
@pytest.mark.parametrize('cls', [Hashable, Unhashable])
390+
def test_callback_disconnect_func_selective(self, pickle, cls):
391+
# Test selectively disconnecting a callback from one signal
392+
# while keeping it connected to another
393+
self.is_empty()
394+
395+
mini_me = cls()
396+
397+
# Connect same function to multiple signals
398+
self.callbacks.connect('signal1', mini_me.dummy)
399+
self.callbacks.connect('signal2', mini_me.dummy)
400+
assert len(list(self.callbacks._func_cid_map)) == 2
401+
402+
# Disconnect from only signal1
403+
self.callbacks.disconnect(mini_me.dummy, signal='signal1')
404+
405+
# Should still have one callback registered (on signal2)
406+
assert len(list(self.callbacks._func_cid_map)) == 1
407+
assert 'signal2' in self.callbacks.callbacks
408+
assert 'signal1' not in self.callbacks.callbacks
409+
410+
# Disconnect from signal2
411+
self.callbacks.disconnect(mini_me.dummy, signal='signal2')
412+
413+
# Now all should be removed
414+
self.is_empty()
415+
362416
@pytest.mark.parametrize('pickle', [True, False])
363417
@pytest.mark.parametrize('cls', [Hashable, Unhashable])
364418
def test_registration_on_non_empty_registry(self, pickle, cls):

0 commit comments

Comments
 (0)