Realtime In-Place Logger
Useful for analysis of data that's changing in real time. Prints properties of interest in-place to the terminal, preventing the need to keep up with quickly scrolling log lines.
See also: the same code in JS.
import sys
def clear_lines(lines):
for _ in range(lines):
sys.stdout.write("\x1b[2K") # Clear the current line
sys.stdout.write("\x1b[A") # Move cursor up one line
sys.stdout.write("\x1b[2K") # Clear the previous line
sys.stdout.flush()
class SingletonMeta(type):
"""
A metaclass for creating singleton classes.
"""
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
# If an instance does not already exist, create one and store it.
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class RealtimeLogger(metaclass=SingletonMeta):
def __init__(self, num_properties: int):
self.num_properties = num_properties
self.has_printed = False
self._data = {}
def __getitem__(self, key):
return self._data[key]
def __setitem__(self, key, value):
self._data[key] = value
def print(self):
keys = list(self._data.keys())
if len(keys) != self.num_properties:
print(f"Warning: Set num_properties to {len(keys)}")
if self.has_printed:
clear_lines(self.num_properties)
for i in range(self.num_properties):
k = keys[i]
print(f'{k}: {self._data[k]}')
self.has_printed = True
Example
viz = RealtimeLogger(2)
viz['my variable'] = 42
viz['Something else'] = 50
viz.print()
for i in range(6):
viz['my variable'] = i
viz.print()
Prints the following. Notice that, despite the fact that print()
is invoked 6 times, there are only two log lines. This is because viz
only has 2 properties, and they're printed in-place upon each invocation.
my variable: 5
Something else: 50