Search code examples
pythonwindowsmultithreadingpython-multithreading

How to use Threading to print in two different places Python?


So I'm on a Windows, using multithreading, along with datetime and blessed to have a constant clock in the upper right-hand corner of the Terminal. To display the clock in the right-hand Terminal, I use a function, which I called displayTime. Here is the code for that function:

from blessed import Terminal
from datetime import datetime


term = Terminal()

def displayTime():
    print(term.home)
    loc = term.get_location()
    while True:
        currentTime = datetime.now()
        a = datetime(currentTime.year, currentTime.month, currentTime.day, currentTime.hour, currentTime.minute, currentTime.second)
        with term.location():
            print(term.move_y(loc[0]))
            print(str(a))
        time.sleep(1)

Now this works, but I couldn't do anything else, such as print() or input(). So I found threading, which, in theory, should allow me to run other code.

So I created a thread:

timethread = threading.Thread(target=displayTime)
timethread.start()

And tested it out, which worked the same. However, when I added this line:

input("Testing multithreading >> ")

To try and write something, it did not work. In fact, the clock disappeared, and I was unable to input any text. I'm now looking for anyway to still have input, along with a clock.


Solution

  • All input and output to the terminal takes place over two (ok, 3 counting stderr) streams of data - Libraries that allow one to position text, set-up colors, and such, embed special codes (known as "ANSI codes" - except on Windows CMD where attributes are set by side-channel APIs) on these streams so that subsequent characters printed are on the new locations or with changed attributes.

    When printing from another thread, even though the processing of the code containing the "print" may be concurrent, the data content output will get into the same stream (sys.stdout by default)

    So, to answer a generic "How to use Threading to print in two different places Python?" - this can be done, but only with a special handling of stdout, by replacing the builtin print function (or rather, sys.stdout itself) by a thread-aware object that would "know" to preserve the terminal state for each thread. It ultimately would have to parse and understand ANSI sequences, and restore the terminal and cursor state to each printing thread. So, feasible, but definetelly a major project.

    On the other hand, it is likely that since you are using the "blessed" library, it offers calls of its own to allow printing and data inputing in parallel to displaying its clock - we can check its docs and see how it is done. If it is built with multi-threading in mind, it might offer a thread-safe API for print/input - otherwiser, it will likely offer an event based system for single-threaded concurrency, not unlike (or perhaps based on) asyncio - and you'd have to write your code accordingly.

    Oh well - https://blessed.readthedocs.io/en/latest/keyboard.html - sorry for that, it converts the terminal to unuffered mode, in order to be able to react to each keystroke (as opposed to having to wait for "enter") and do not seems to offer an equivalent to "input". You'd have to assemble your own by using the built-in "inkey()" call.

    I suggest you use a different library for terminal access: there is terminedia https://pypi.org/project/terminedia/ (my own project, though underdocumented at this point), textual https://textual.textualize.io/ (more complete), and even Python's built-in "curses" https://docs.python.org/3/howto/curses.html (not on windows) : all three of those can acomplish complex text entry while updating a watch in a fixed position on the screen.