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

Skip to content

Commit b9d9049

Browse files
committed
Use CallbackRegistry for all Widget event observers.
This simplifies processing, fixes memory leaks with weak references, and fixes bugs like being unable to disconnect a callback in its own code.
1 parent da48bcd commit b9d9049

2 files changed

Lines changed: 56 additions & 93 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Widgets use ``CallbackRegistry`` to save callbacks
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
`.Widget`\s with event observers now use a `.CallbackRegistry` for storing
5+
callbacks. This is consistent with canvas event callbacks, and fixes some bugs
6+
in widget callback handling.
7+
8+
Due to this change, callback methods are now stored as weak references, which
9+
means you must keep a reference to the associated object. Otherwise it may be
10+
garbage collected.

lib/matplotlib/widgets.py

Lines changed: 46 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,7 @@ def __init__(self, ax, label, image=None,
180180
horizontalalignment='center',
181181
transform=ax.transAxes)
182182

183-
self._cnt = 0
184-
self._observers = {}
183+
self._observers = cbook.CallbackRegistry()
185184

186185
self.connect_event('button_press_event', self._click)
187186
self.connect_event('button_release_event', self._release)
@@ -196,12 +195,13 @@ def __init__(self, ax, label, image=None,
196195
@cbook.deprecated("3.4")
197196
@property
198197
def cnt(self):
199-
return self._cnt
198+
# Not real, but close enough.
199+
return len(self._observers.callbacks['clicked'])
200200

201201
@cbook.deprecated("3.4")
202202
@property
203203
def observers(self):
204-
return self._observers
204+
return self._observers.callbacks['clicked']
205205

206206
def _click(self, event):
207207
if (self.ignore(event)
@@ -219,8 +219,7 @@ def _release(self, event):
219219
if (not self.eventson
220220
or event.inaxes != self.ax):
221221
return
222-
for cid, func in self._observers.items():
223-
func(event)
222+
self._observers.process('clicked', event)
224223

225224
def _motion(self, event):
226225
if self.ignore(event):
@@ -237,17 +236,11 @@ def on_clicked(self, func):
237236
238237
Returns a connection id, which can be used to disconnect the callback.
239238
"""
240-
cid = self._cnt
241-
self._observers[cid] = func
242-
self._cnt += 1
243-
return cid
239+
return self._observers.connect('clicked', func)
244240

245241
def disconnect(self, cid):
246242
"""Remove the callback function with connection id *cid*."""
247-
try:
248-
del self._observers[cid]
249-
except KeyError:
250-
pass
243+
self._observers.disconnect(cid)
251244

252245

253246
class Slider(AxesWidget):
@@ -397,20 +390,20 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt=None,
397390
verticalalignment='center',
398391
horizontalalignment='left')
399392

400-
self._cnt = 0
401-
self._observers = {}
393+
self._observers = cbook.CallbackRegistry()
402394

403395
self.set_val(valinit)
404396

405397
@cbook.deprecated("3.4")
406398
@property
407399
def cnt(self):
408-
return self._cnt
400+
# Not real, but close enough.
401+
return len(self._observers.callbacks['changed'])
409402

410403
@cbook.deprecated("3.4")
411404
@property
412405
def observers(self):
413-
return self._observers
406+
return self._observers.callbacks['changed']
414407

415408
def _value_in_bounds(self, val):
416409
"""Makes sure *val* is with given bounds."""
@@ -494,8 +487,7 @@ def set_val(self, val):
494487
self.val = val
495488
if not self.eventson:
496489
return
497-
for cid, func in self._observers.items():
498-
func(val)
490+
self._observers.process('changed', val)
499491

500492
def on_changed(self, func):
501493
"""
@@ -513,10 +505,7 @@ def on_changed(self, func):
513505
int
514506
Connection id (which can be used to disconnect *func*)
515507
"""
516-
cid = self._cnt
517-
self._observers[cid] = func
518-
self._cnt += 1
519-
return cid
508+
return self._observers.connect('changed', func)
520509

521510
def disconnect(self, cid):
522511
"""
@@ -527,10 +516,7 @@ def disconnect(self, cid):
527516
cid : int
528517
Connection id of the observer to be removed
529518
"""
530-
try:
531-
del self._observers[cid]
532-
except KeyError:
533-
pass
519+
self._observers.disconnect(cid)
534520

535521
def reset(self):
536522
"""Reset the slider to the initial value"""
@@ -625,18 +611,18 @@ def __init__(self, ax, labels, actives=None):
625611

626612
self.connect_event('button_press_event', self._clicked)
627613

628-
self._cnt = 0
629-
self._observers = {}
614+
self._observers = cbook.CallbackRegistry()
630615

631616
@cbook.deprecated("3.4")
632617
@property
633618
def cnt(self):
634-
return self._cnt
619+
# Not real, but close enough.
620+
return len(self._observers.callbacks['clicked'])
635621

636622
@cbook.deprecated("3.4")
637623
@property
638624
def observers(self):
639-
return self._observers
625+
return self._observers.callbacks['clicked']
640626

641627
def _clicked(self, event):
642628
if self.ignore(event) or event.button != 1 or event.inaxes != self.ax:
@@ -675,8 +661,7 @@ def set_active(self, index):
675661

676662
if not self.eventson:
677663
return
678-
for cid, func in self._observers.items():
679-
func(self.labels[index].get_text())
664+
self._observers.process('clicked', self.labels[index].get_text())
680665

681666
def get_status(self):
682667
"""
@@ -690,17 +675,11 @@ def on_clicked(self, func):
690675
691676
Returns a connection id, which can be used to disconnect the callback.
692677
"""
693-
cid = self._cnt
694-
self._observers[cid] = func
695-
self._cnt += 1
696-
return cid
678+
return self._observers.connect('clicked', func)
697679

698680
def disconnect(self, cid):
699681
"""Remove the observer with connection id *cid*."""
700-
try:
701-
del self._observers[cid]
702-
except KeyError:
703-
pass
682+
self._observers.disconnect(cid)
704683

705684

706685
class TextBox(AxesWidget):
@@ -760,9 +739,7 @@ def __init__(self, ax, label, initial='',
760739
self.DIST_FROM_LEFT, 0.5, initial, transform=self.ax.transAxes,
761740
verticalalignment='center', horizontalalignment='left')
762741

763-
self._cnt = 0
764-
self._change_observers = {}
765-
self._submit_observers = {}
742+
self._observers = cbook.CallbackRegistry()
766743

767744
ax.set(
768745
xlim=(0, 1), ylim=(0, 1), # s.t. cursor appears from first click.
@@ -788,17 +765,18 @@ def __init__(self, ax, label, initial='',
788765
@cbook.deprecated("3.4")
789766
@property
790767
def cnt(self):
791-
return self._cnt
768+
# Not real, but close enough.
769+
return sum(len(d) for d in self._observers.callbacks.values())
792770

793771
@cbook.deprecated("3.4")
794772
@property
795773
def change_observers(self):
796-
return self._change_observers
774+
return self._observers.callbacks['change']
797775

798776
@cbook.deprecated("3.4")
799777
@property
800778
def submit_observers(self):
801-
return self._submit_observers
779+
return self._observers.callbacks['submit']
802780

803781
@property
804782
def text(self):
@@ -828,11 +806,6 @@ def _rendercursor(self):
828806

829807
self.ax.figure.canvas.draw()
830808

831-
def _notify_submit_observers(self):
832-
if self.eventson:
833-
for cid, func in self._submit_observers.items():
834-
func(self.text)
835-
836809
def _release(self, event):
837810
if self.ignore(event):
838811
return
@@ -871,23 +844,20 @@ def _keypress(self, event):
871844
text[self.cursor_index + 1:])
872845
self.text_disp.set_text(text)
873846
self._rendercursor()
874-
self._notify_change_observers()
875-
if key == "enter":
876-
self._notify_submit_observers()
847+
if self.eventson:
848+
self._observers.process('change', self.text)
849+
if key == "enter":
850+
self._observers.process('submit', self.text)
877851

878852
def set_val(self, val):
879853
newval = str(val)
880854
if self.text == newval:
881855
return
882856
self.text_disp.set_text(newval)
883857
self._rendercursor()
884-
self._notify_change_observers()
885-
self._notify_submit_observers()
886-
887-
def _notify_change_observers(self):
888858
if self.eventson:
889-
for cid, func in self._change_observers.items():
890-
func(self.text)
859+
self._observers.process('change', self.text)
860+
self._observers.process('submit', self.text)
891861

892862
def begin_typing(self, x):
893863
self.capturekeystrokes = True
@@ -920,10 +890,10 @@ def stop_typing(self):
920890
self.capturekeystrokes = False
921891
self.cursor.set_visible(False)
922892
self.ax.figure.canvas.draw()
923-
if notifysubmit:
924-
# Because _notify_submit_observers might throw an error in the
925-
# user's code, only call it once we've already done our cleanup.
926-
self._notify_submit_observers()
893+
if notifysubmit and self.eventson:
894+
# Because process() might throw an error in the user's code, only
895+
# call it once we've already done our cleanup.
896+
self._observers.process('submit', self.text)
927897

928898
def position_cursor(self, x):
929899
# now, we have to figure out where the cursor goes.
@@ -968,10 +938,7 @@ def on_text_change(self, func):
968938
969939
A connection id is returned which can be used to disconnect.
970940
"""
971-
cid = self._cnt
972-
self._change_observers[cid] = func
973-
self._cnt += 1
974-
return cid
941+
return self._observers.connect('change', func)
975942

976943
def on_submit(self, func):
977944
"""
@@ -980,18 +947,11 @@ def on_submit(self, func):
980947
981948
A connection id is returned which can be used to disconnect.
982949
"""
983-
cid = self._cnt
984-
self._submit_observers[cid] = func
985-
self._cnt += 1
986-
return cid
950+
return self._observers.connect('submit', func)
987951

988952
def disconnect(self, cid):
989953
"""Remove the observer with connection id *cid*."""
990-
for reg in [self._change_observers, self._submit_observers]:
991-
try:
992-
del reg[cid]
993-
except KeyError:
994-
pass
954+
self._observers.disconnect(cid)
995955

996956

997957
class RadioButtons(AxesWidget):
@@ -1072,18 +1032,18 @@ def __init__(self, ax, labels, active=0, activecolor='blue'):
10721032

10731033
self.connect_event('button_press_event', self._clicked)
10741034

1075-
self._cnt = 0
1076-
self._observers = {}
1035+
self._observers = cbook.CallbackRegistry()
10771036

10781037
@cbook.deprecated("3.4")
10791038
@property
10801039
def cnt(self):
1081-
return self._cnt
1040+
# Not real, but close enough.
1041+
return len(self._observers.callbacks['clicked'])
10821042

10831043
@cbook.deprecated("3.4")
10841044
@property
10851045
def observers(self):
1086-
return self._observers
1046+
return self._observers.callbacks['clicked']
10871047

10881048
def _clicked(self, event):
10891049
if self.ignore(event) or event.button != 1 or event.inaxes != self.ax:
@@ -1121,26 +1081,19 @@ def set_active(self, index):
11211081

11221082
if not self.eventson:
11231083
return
1124-
for cid, func in self._observers.items():
1125-
func(self.labels[index].get_text())
1084+
self._observers.process('clicked', self.labels[index].get_text())
11261085

11271086
def on_clicked(self, func):
11281087
"""
11291088
Connect the callback function *func* to button click events.
11301089
11311090
Returns a connection id, which can be used to disconnect the callback.
11321091
"""
1133-
cid = self._cnt
1134-
self._observers[cid] = func
1135-
self._cnt += 1
1136-
return cid
1092+
return self._observers.connect('clicked', func)
11371093

11381094
def disconnect(self, cid):
11391095
"""Remove the observer with connection id *cid*."""
1140-
try:
1141-
del self._observers[cid]
1142-
except KeyError:
1143-
pass
1096+
self._observers.disconnect(cid)
11441097

11451098

11461099
class SubplotTool(Widget):

0 commit comments

Comments
 (0)