A ContextManager for keeping threaded output associated with a cell, even after moving on.

In [1]:
import sys
import threading
import time
from contextlib import contextmanager
In [2]:
# we need a lock, so that other threads don't snatch control
# while we have set a temporary parent
stdout_lock = threading.Lock()

@contextmanager
def set_stdout_parent(parent):
    """a context manager for setting a particular parent for sys.stdout
    
    the parent determines the destination cell of output
    """
    save_parent = sys.stdout.parent_header
    with stdout_lock:
        sys.stdout.parent_header = parent
        try:
            yield
        finally:
            # the flush is important, because that's when the parent_header actually has its effect
            sys.stdout.flush()
            sys.stdout.parent_header = save_parent

Just use this tic as a marker, to show that we really are printing to two cells simultaneously

In [3]:
tic = time.time()
In [4]:
class counterThread(threading.Thread):
    def run(self):
        # record the parent when the thread starts
        thread_parent = sys.stdout.parent_header
        for i in range(3):
            time.sleep(2)
            # then ensure that the parent is the same as when the thread started
            # every time we print
            with set_stdout_parent(thread_parent):
                print i, "%.2f" % (time.time() - tic)
In [5]:
for i in range(3):
    counterThread().start()
0 2.05
0 2.05
0 2.05
1 4.05
1 4.05
1 4.05
2 6.06
2 6.06
2 6.06
In [6]:
for i in range(3):
    counterThread().start()
0 2.07
0 2.07
0 2.07
1 4.07
1 4.07
1 4.08
2 6.08
2 6.08
2 6.08