| '
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()
|