Package: aio-stdout
aio_stdoutAsynchronous Input Output - StdoutThe purpose of this package is to provide asynchronous variants of the builtininputandprintfunctions.printis known to be relatively slow compared to other operations.inputis even slower because it has to wait for user input. While these slow IO operations are being ran, code usingasyncioshould be able to continuously run.PIP InstallingFor Unix/macOS:python3 -m pip install aio-stdoutFor Windows:py -m pip install aio-stdoutainput and aprintWithaio_stdout, theaio_stdout.ainputandaio_stdout.aprintfunctions provide easy to use functionality with organized behaviour.importasynciofromaio_stdoutimportainput,aprintasyncdefcountdown(n:int)->None:"""Count down from `n`, taking `n` seconds to run."""foriinrange(n,0,-1):awaitaprint(i)awaitasyncio.sleep(1)asyncdefget_name()->str:"""Ask the user for their name."""name=awaitainput("What is your name? ")awaitaprint(f"Your name is{name}.")returnnameasyncdefmain()->None:awaitasyncio.gather(countdown(15),get_name())if__name__=="__main__":asyncio.run(main())Example output:15
What is your name? Jane
14
13
12
11
10
9
8
Your name is Jane.
7
6
5
4
3
2
1Notice that while the prompt"What is your name? "is being waited for, thecountdowncontinues toaprintin the background, without becoming blocked. Thecountdowndoes not, however, display its results until theainputis completed. Instead it waits for theainputto finish before flushing out all of the queued messages.It is worth noting that with naive threading, a normal attempt to useprintwhile waiting on aninputleads to overlapping messages. Fixing this behavior requires a lot more work than should be needed to use a simpleprintorinputfunction, which is why this package exists. To remedy this problem, queues are used to store messages until they are ready to be printed.IO LocksAlthough the asynchronization behaviors ofainputandaprintare nice, sometimes we want to be able to synchronize our messages even more. IO locks provide a way to group messages together, locking the globalaio_stdoutqueues until it finishes or yields access.importasynciofromaio_stdoutimportIOLock,ainput,aprintasyncdefcountdown(n:int)->None:"""Count down from `n`, taking `n` seconds to run."""asyncwithIOLock(n=5)aslock:foriinrange(n,0,-1):awaitlock.aprint(i)awaitasyncio.sleep(1)asyncdefget_name()->str:"""Ask the user for their name."""asyncwithIOLock()aslock:name=awaitlock.ainput("What is your name? ")awaitlock.aprint(f"Your name is{name}.")returnnameasyncdefmain()->None:awaitasyncio.gather(countdown(15),get_name())if__name__=="__main__":asyncio.run(main())Let's try the example again now using the new locks:15
14
13
12
11
What is your name? Jane
Your name is Jane.
10
9
8
7
6
5
4
3
2
1Notice that this time thecountdowndoes not immediately yield to theget_name. Instead, it runs 5 messages before yielding control over toget_name. Now, after thelock.ainputfinishes, it does not yield tocountdown. Instead, it runs its ownlock.aprintfirst. In the meantime,countdowncontinues to run in the background and flushes all of its buffered messages afterwards.FlushingSince messages may be delayed, it is possible for your asynchronous code to finish running before all messages are displayed, producing confusing results. As such, the best recommended practice is to flush frommainbefore terminating.fromaio_stdoutimportflush@flushasyncdefmain()->None:...Final ExampleCombining all best practices, the final example should look something like this:importasynciofromaio_stdoutimportIOLock,ainput,aprint,flushasyncdefcountdown(n:int)->None:"""Count down from `n`, taking `n` seconds to run."""foriinrange(n,0,-1):awaitaprint(i)awaitasyncio.sleep(1)asyncdefget_name()->str:"""Ask the user for their name."""asyncwithIOLock()aslock:name=awaitlock.ainput("What is your name? ")awaitlock.aprint(f"Your name is{name}.")returnname@flushasyncdefmain()->None:awaitasyncio.gather(countdown(15),get_name())if__name__=="__main__":asyncio.run(main())Common GotchasUsinginputorprintinstead ofainputandaprintwill push a message immediately to the console, potentially conflicting withainputoraprint.Usingainputoraprintinstead oflock.ainputandlock.aprintmay producedeadlockdue to having to wait for the lock to release. As such, thelockis equipped with a defaulttimeoutlimit of 10 seconds to avoid deadlock and explain to users this potential problem.