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

Skip to content

Commit e2ed7e0

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 e2ed7e0

File tree

1 file changed

+46
-92
lines changed

1 file changed

+46
-92
lines changed

lib/matplotlib/widgets.py

Lines changed: 46 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def __init__(self, ax, label, image=None,
181181
transform=ax.transAxes)
182182

183183
self._cnt = 0
184-
self._observers = {}
184+
self._observers = cbook.CallbackRegistry()
185185

186186
self.connect_event('button_press_event', self._click)
187187
self.connect_event('button_release_event', self._release)
@@ -196,12 +196,13 @@ def __init__(self, ax, label, image=None,
196196
@cbook.deprecated("3.4")
197197
@property
198198
def cnt(self):
199-
return self._cnt
199+
# Not real, but close enough.
200+
return len(self._observers.callbacks['clicked'])
200201

201202
@cbook.deprecated("3.4")
202203
@property
203204
def observers(self):
204-
return self._observers
205+
return self._observers.callbacks['clicked']
205206

206207
def _click(self, event):
207208
if (self.ignore(event)
@@ -219,8 +220,7 @@ def _release(self, event):
219220
if (not self.eventson
220221
or event.inaxes != self.ax):
221222
return
222-
for cid, func in self._observers.items():
223-
func(event)
223+
self._observers.process('clicked', event)
224224

225225
def _motion(self, event):
226226
if self.ignore(event):
@@ -237,17 +237,11 @@ def on_clicked(self, func):
237237
238238
Returns a connection id, which can be used to disconnect the callback.
239239
"""
240-
cid = self._cnt
241-
self._observers[cid] = func
242-
self._cnt += 1
243-
return cid
240+
return self._observers.connect('clicked', func)
244241

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

252246

253247
class Slider(AxesWidget):
@@ -397,20 +391,20 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt=None,
397391
verticalalignment='center',
398392
horizontalalignment='left')
399393

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

403396
self.set_val(valinit)
404397

405398
@cbook.deprecated("3.4")
406399
@property
407400
def cnt(self):
408-
return self._cnt
401+
# Not real, but close enough.
402+
return len(self._observers.callbacks['changed'])
409403

410404
@cbook.deprecated("3.4")
411405
@property
412406
def observers(self):
413-
return self._observers
407+
return self._observers.callbacks['changed']
414408

415409
def _value_in_bounds(self, val):
416410
"""Makes sure *val* is with given bounds."""
@@ -494,8 +488,7 @@ def set_val(self, val):
494488
self.val = val
495489
if not self.eventson:
496490
return
497-
for cid, func in self._observers.items():
498-
func(val)
491+
self._observers.process('changed', val)
499492

500493
def on_changed(self, func):
501494
"""
@@ -513,10 +506,7 @@ def on_changed(self, func):
513506
int
514507
Connection id (which can be used to disconnect *func*)
515508
"""
516-
cid = self._cnt
517-
self._observers[cid] = func
518-
self._cnt += 1
519-
return cid
509+
return self._observers.connect('changed', func)
520510

521511
def disconnect(self, cid):
522512
"""
@@ -527,10 +517,7 @@ def disconnect(self, cid):
527517
cid : int
528518
Connection id of the observer to be removed
529519
"""
530-
try:
531-
del self._observers[cid]
532-
except KeyError:
533-
pass
520+
self._observers.disconnect(cid)
534521

535522
def reset(self):
536523
"""Reset the slider to the initial value"""
@@ -625,18 +612,18 @@ def __init__(self, ax, labels, actives=None):
625612

626613
self.connect_event('button_press_event', self._clicked)
627614

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

631617
@cbook.deprecated("3.4")
632618
@property
633619
def cnt(self):
634-
return self._cnt
620+
# Not real, but close enough.
621+
return len(self._observers.callbacks['clicked'])
635622

636623
@cbook.deprecated("3.4")
637624
@property
638625
def observers(self):
639-
return self._observers
626+
return self._observers.callbacks['clicked']
640627

641628
def _clicked(self, event):
642629
if self.ignore(event) or event.button != 1 or event.inaxes != self.ax:
@@ -675,8 +662,7 @@ def set_active(self, index):
675662

676663
if not self.eventson:
677664
return
678-
for cid, func in self._observers.items():
679-
func(self.labels[index].get_text())
665+
self._observers.process('clicked', self.labels[index].get_text())
680666

681667
def get_status(self):
682668
"""
@@ -690,17 +676,11 @@ def on_clicked(self, func):
690676
691677
Returns a connection id, which can be used to disconnect the callback.
692678
"""
693-
cid = self._cnt
694-
self._observers[cid] = func
695-
self._cnt += 1
696-
return cid
679+
self._observers.connect('clicked', func)
697680

698681
def disconnect(self, cid):
699682
"""Remove the observer with connection id *cid*."""
700-
try:
701-
del self._observers[cid]
702-
except KeyError:
703-
pass
683+
self._observers.disconnect(cid)
704684

705685

706686
class TextBox(AxesWidget):
@@ -760,9 +740,7 @@ def __init__(self, ax, label, initial='',
760740
self.DIST_FROM_LEFT, 0.5, initial, transform=self.ax.transAxes,
761741
verticalalignment='center', horizontalalignment='left')
762742

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

767745
ax.set(
768746
xlim=(0, 1), ylim=(0, 1), # s.t. cursor appears from first click.
@@ -788,17 +766,18 @@ def __init__(self, ax, label, initial='',
788766
@cbook.deprecated("3.4")
789767
@property
790768
def cnt(self):
791-
return self._cnt
769+
# Not real, but close enough.
770+
return sum(len(d) for d in self._observers.callbacks.values())
792771

793772
@cbook.deprecated("3.4")
794773
@property
795774
def change_observers(self):
796-
return self._change_observers
775+
return self._observers.callbacks['change']
797776

798777
@cbook.deprecated("3.4")
799778
@property
800779
def submit_observers(self):
801-
return self._submit_observers
780+
return self._observers.callbacks['submit']
802781

803782
@property
804783
def text(self):
@@ -828,11 +807,6 @@ def _rendercursor(self):
828807

829808
self.ax.figure.canvas.draw()
830809

831-
def _notify_submit_observers(self):
832-
if self.eventson:
833-
for cid, func in self._submit_observers.items():
834-
func(self.text)
835-
836810
def _release(self, event):
837811
if self.ignore(event):
838812
return
@@ -871,23 +845,20 @@ def _keypress(self, event):
871845
text[self.cursor_index + 1:])
872846
self.text_disp.set_text(text)
873847
self._rendercursor()
874-
self._notify_change_observers()
875-
if key == "enter":
876-
self._notify_submit_observers()
848+
if self.eventson:
849+
self._observers.process('change', self.text)
850+
if key == "enter":
851+
self._observers.process('submit', self.text)
877852

878853
def set_val(self, val):
879854
newval = str(val)
880855
if self.text == newval:
881856
return
882857
self.text_disp.set_text(newval)
883858
self._rendercursor()
884-
self._notify_change_observers()
885-
self._notify_submit_observers()
886-
887-
def _notify_change_observers(self):
888859
if self.eventson:
889-
for cid, func in self._change_observers.items():
890-
func(self.text)
860+
self._observers.process('change', self.text)
861+
self._observers.process('submit', self.text)
891862

892863
def begin_typing(self, x):
893864
self.capturekeystrokes = True
@@ -920,10 +891,10 @@ def stop_typing(self):
920891
self.capturekeystrokes = False
921892
self.cursor.set_visible(False)
922893
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()
894+
if notifysubmit and self.eventson:
895+
# Because process() might throw an error in the user's code, only
896+
# call it once we've already done our cleanup.
897+
self._observers.process('submit', self.text)
927898

928899
def position_cursor(self, x):
929900
# now, we have to figure out where the cursor goes.
@@ -968,10 +939,7 @@ def on_text_change(self, func):
968939
969940
A connection id is returned which can be used to disconnect.
970941
"""
971-
cid = self._cnt
972-
self._change_observers[cid] = func
973-
self._cnt += 1
974-
return cid
942+
self._observers.connect('change', func)
975943

976944
def on_submit(self, func):
977945
"""
@@ -980,18 +948,11 @@ def on_submit(self, func):
980948
981949
A connection id is returned which can be used to disconnect.
982950
"""
983-
cid = self._cnt
984-
self._submit_observers[cid] = func
985-
self._cnt += 1
986-
return cid
951+
self._observers.process('submit', func)
987952

988953
def disconnect(self, cid):
989954
"""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
955+
self._observers.disconnect(cid)
995956

996957

997958
class RadioButtons(AxesWidget):
@@ -1072,18 +1033,18 @@ def __init__(self, ax, labels, active=0, activecolor='blue'):
10721033

10731034
self.connect_event('button_press_event', self._clicked)
10741035

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

10781038
@cbook.deprecated("3.4")
10791039
@property
10801040
def cnt(self):
1081-
return self._cnt
1041+
# Not real, but close enough.
1042+
return len(self._observers.callbacks['clicked'])
10821043

10831044
@cbook.deprecated("3.4")
10841045
@property
10851046
def observers(self):
1086-
return self._observers
1047+
return self._observers.callbacks['clicked']
10871048

10881049
def _clicked(self, event):
10891050
if self.ignore(event) or event.button != 1 or event.inaxes != self.ax:
@@ -1121,26 +1082,19 @@ def set_active(self, index):
11211082

11221083
if not self.eventson:
11231084
return
1124-
for cid, func in self._observers.items():
1125-
func(self.labels[index].get_text())
1085+
self._observers.process('clicked', self.labels[index].get_text())
11261086

11271087
def on_clicked(self, func):
11281088
"""
11291089
Connect the callback function *func* to button click events.
11301090
11311091
Returns a connection id, which can be used to disconnect the callback.
11321092
"""
1133-
cid = self._cnt
1134-
self._observers[cid] = func
1135-
self._cnt += 1
1136-
return cid
1093+
self._observers.connect('clicked', func)
11371094

11381095
def disconnect(self, cid):
11391096
"""Remove the observer with connection id *cid*."""
1140-
try:
1141-
del self._observers[cid]
1142-
except KeyError:
1143-
pass
1097+
self._observers.disconnect(cid)
11441098

11451099

11461100
class SubplotTool(Widget):

0 commit comments

Comments
 (0)