import inspect import re import weakref import ipywidgets HEADER = '
NameTypeValue
' FOOTER = '
' SEP = '' LINE = '{0}{1}{2}' IGNORE = ["In", "Out", "exit", "quit", "get_ipython"] RE_DIGITS = re.compile("([0-9]+)") class Singleton(type): def __init__(cls, name, bases, namespace): super().__init__(name, bases, namespace) # creates the class cls.__signature__ = inspect.signature(cls.__init__) # restore the constructor signature (instead of that of __call__ below) cls.__instance__ = lambda: None # fake dead weakref def __call__(cls, *args, **kwargs): inst = cls.__instance__() if not inst: inst = super().__call__(*args, **kwargs) # creates the instance (calls __new__ and __init__ methods) cls.__instance__ = weakref.ref(inst) return inst class VariableInspector(object, metaclass=Singleton): def __init__(self, ipython=None): self._box = ipywidgets.Box() self._box.layout.overflow_y = "scroll" self._table = ipywidgets.HTML(value="Loading...") self._box.children = [self._table] self._ipython = ipython or get_ipython() self.start() def start(self): """Add update callback if not already registered""" if self.is_running: raise RuntimeError("Cannot start. Update callback is already registered") self._ipython.events.register("post_run_cell", self._update) def stop(self): """Remove update callback if registered""" if not self.is_running: raise RuntimeError("Cannot stop. Update callback is not registered") self._ipython.events.unregister("post_run_cell", self._update) @property def is_running(self): """Test if update callback is registered""" return self._update in self._ipython.events.callbacks["post_run_cell"] def _update(self, _): """Fill table with variable information""" namespace = self._ipython.user_ns.items() lines = (format_line(k, v) for k, v in sorted_naturally(namespace) if is_good_entry(k, v)) self._table.value = HEADER + SEP.join(lines) + FOOTER def _ipython_display_(self): """Called when display() or pyout is used to display the Variable Inspector""" try: self._box._ipython_display_() except: pass def format_line(k, v): return LINE.format(k, typename(v), v) def sorted_naturally(iterable, reverse=False): natural = lambda item: [int(c) if c.isdigit() else c.casefold() for c in RE_DIGITS.split(str(item))] return sorted(iterable, key=natural, reverse=reverse) def is_good_entry(k, v): return not k.startswith("_") and k not in IGNORE and not isinstance(v, VariableInspector) def typename(obj): return type(obj).__name__ inspector = VariableInspector()