66in the Jupyter notebook front-end.
77"""
88import os
9- import typing
109import weakref
1110from contextlib import contextmanager
1211from collections .abc import Iterable
@@ -42,23 +41,33 @@ def envset(name, default):
4241# for a discussion on using weak references see:
4342# https://github.com/jupyter-widgets/ipywidgets/issues/1345
4443
44+ # A global to store all widget instances so that it can be swapped out when
45+ # enabling/disabling weakreferences
46+ _widget_instances = {}
47+
48+
4549def enable_weakreference ():
46- """Use a WeakValueDictionary to store references to Widget instances .
50+ """Configure the module to only maintain a weakreference between the comm_id and widget for all widgets .
4751
48- A strong reference must be kept to widgets.
52+ With this enabled the user must maintain a strong reference to widgets. The
53+ advantage being that memory leaks prevented in long running programs.
4954 """
50- if not isinstance (Widget ._instances , weakref .WeakValueDictionary ):
51- Widget ._instances = weakref .WeakValueDictionary (Widget ._instances )
55+ global _widget_instances
56+ if not isinstance (_widget_instances , weakref .WeakValueDictionary ):
57+ _widget_instances = weakref .WeakValueDictionary (_widget_instances )
58+ # Widget.__dict__["_instances"] = weakref.WeakValueDictionary()
59+
5260
5361def disable_weakreference ():
54- """Use a standard dictionary to store references to Widget instances (default behavior) .
62+ """Configure the module to only maintain a strong reference between the comm_id and widget for all widgets .
5563
56- Note: this is the default setting and maintains a strong reference to the
57- the widget preventing automatic garbage collection. When the widget is closed
58- it can be garbage collected.
64+ !!! Note:
65+ This is the default behavior. The method `Widget.close` should be called when it is no longer required.
5966 """
60- if isinstance (Widget ._instances , weakref .WeakValueDictionary ):
61- Widget ._instances = dict (Widget ._instances )
67+ global _widget_instances
68+ if isinstance (_widget_instances , weakref .WeakValueDictionary ):
69+ _widget_instances = dict (_widget_instances )
70+
6271
6372def _widget_to_json (x , obj ):
6473 if isinstance (x , Widget ):
@@ -75,8 +84,11 @@ def _json_to_widget(x, obj):
7584 return {k : _json_to_widget (v , obj ) for k , v in x .items ()}
7685 elif isinstance (x , (list , tuple )):
7786 return [_json_to_widget (v , obj ) for v in x ]
78- elif isinstance (x , str ) and x .startswith ("IPY_MODEL_" ) and x [10 :] in Widget ._instances :
79- return Widget ._instances [x [10 :]]
87+ elif isinstance (x , str ) and x .startswith ("IPY_MODEL_" ):
88+ try :
89+ return _widget_instances [x [10 :]]
90+ except (KeyError , IndexError ):
91+ pass
8092 else :
8193 return x
8294
@@ -308,13 +320,18 @@ class Widget(LoggingHasTraits):
308320 _widget_construction_callback = None
309321 _control_comm = None
310322
311- _instances : typing .ClassVar [typing .MutableMapping [str , "Widget" ]] = {}
312323
313324 @classmethod
314325 def close_all (cls ):
315- for widget in list (Widget ._instances .values ()):
326+ while _widget_instances :
327+ _ , widget = _widget_instances .popitem ()
316328 widget .close ()
317329
330+ @staticmethod
331+ def all_widgets () -> dict [str , "Widget" ]:
332+ "Returns a dict mapping `comm_id` to Widget of all Widget instances."
333+ return dict (_widget_instances )
334+
318335 @staticmethod
319336 def on_widget_constructed (callback ):
320337 """Registers a callback to be called when a widget is constructed.
@@ -354,7 +371,7 @@ def _handle_control_comm_msg(cls, msg):
354371 if method == 'request_states' :
355372 # Send back the full widgets state
356373 cls .get_manager_state ()
357- widgets = cls . _instances .values ()
374+ widgets = _widget_instances .values ()
358375 full_state = {}
359376 drop_defaults = False
360377 for widget in widgets :
@@ -405,7 +422,7 @@ def get_manager_state(cls, drop_defaults=False, widgets=None):
405422 """
406423 state = {}
407424 if widgets is None :
408- widgets = cls . _instances .values ()
425+ widgets = _widget_instances .values ()
409426 for widget in widgets :
410427 state [widget .model_id ] = widget ._get_embed_state (drop_defaults = drop_defaults )
411428 return {'version_major' : 2 , 'version_minor' : 0 , 'state' : state }
@@ -516,9 +533,10 @@ def _comm_changed(self, change):
516533 if change ['old' ]:
517534 change ['old' ].on_msg (None )
518535 change ['old' ].close ()
519- self ._instances .pop (change ['old' ].comm_id , None )
536+ if _widget_instances : # This check is needed to avoid errors on cleanup
537+ _widget_instances .pop (change ["old" ].comm_id , None )
520538 if change ['new' ]:
521- self . _instances [change ["new" ].comm_id ] = self
539+ _widget_instances [change ["new" ].comm_id ] = self
522540 self ._model_id = change ["new" ].comm_id
523541
524542 # prevent memory leaks by using a weak reference to self.
0 commit comments