How to use the await keyword in the Python REPL without asyncio.run()
The Python REPL (read-eval-print loop), which is Python's interactive interpreter, is a great way for quickly testing simple Python commands. I use it quite often as a powerful command-line calculator, but also for exploring new Python libraries.
Many interesting Python libraries use asynchronous I/O with asyncio. This means that, instead of just calling a function directly, you have to await
a coroutine. However, if you try this in the REPL, you encounter the following error message:
$ python Python 3.10.6 (main, May 29 2023, 11:10:38) [GCC 11.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import asyncio >>> await asyncio.sleep(5) File "<stdin>", line 1 SyntaxError: 'await' outside function >>>
This is expected, because this line of code would never run in a Python script either. You would need to define a top-level coroutine with async def
that calls one or more coroutines with await
, and run the top-level coroutine with asyncio.run()
. The canonical "Hello world" example from the Python documentation of coroutines looks something like this:
$ python Python 3.10.6 (main, May 29 2023, 11:10:38) [GCC 11.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import asyncio >>> async def main(): ... print("Hello") ... await asyncio.sleep(5) ... print("world") ... >>> asyncio.run(main()) Hello world >>>
There's a five-second delay between the output of "Hello" and "world".
This seems a bit cumbersome. If you just want to call the asyncio.sleep()
coroutine, you can simplify this to:
$ python Python 3.10.6 (main, May 29 2023, 11:10:38) [GCC 11.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import asyncio >>> asyncio.run(asyncio.sleep(5)) >>>
This is already better! However, it still means that every time you want to call a coroutine, you need to remember to wrap it inside an asyncio.run()
call.
Fortunately, Python 3.8 introduced a top-level await if you run the asyncio
module as a script:
$ python -m asyncio asyncio REPL 3.10.6 (main, May 29 2023, 11:10:38) [GCC 11.3.0] on linux Use "await" directly instead of "asyncio.run()". Type "help", "copyright", "credits" or "license" for more information. >>> import asyncio >>> await asyncio.sleep(5) >>>
The REPL helpfully mentions Use "await" directly instead of "asyncio.run()".
before importing the asyncio
module. [1] Then you can simply type await asyncio.sleep(5)
without having to call asyncio.run()
.
Although this might not seem like much of an improvement in this particular case, when using asynchronous libraries in a Python REPL, having to add asyncio.run()
for every coroutine call quickly becomes tedious.
For example, I can now easily request the Bluetooth adapters on my system (using Home Assistant's bluetooth-adapters package):
$ python -m asyncio asyncio REPL 3.10.6 (main, May 29 2023, 11:10:38) [GCC 11.3.0] on linux Use "await" directly instead of "asyncio.run()". Type "help", "copyright", "credits" or "license" for more information. >>> import asyncio >>> from bluetooth_adapters import get_adapters >>> adapters = get_adapters() >>> await adapters.refresh() >>> adapters.adapters {'hci0': {'address': '9C:FC:E8:XX:XX:XX', 'sw_version': 'tux', 'hw_version': 'usb:v1D6Bp0246d0540', 'passive_scan': True, 'manufacturer': 'Intel Corporate', 'product': '0029', 'vendor_id': '8087', 'product_id': '0029'}}
Or, I can conduct a quick scan for Bluetooth Low Energy (BLE) devices in the vicinity (using the Bleak package):
$ python -m asyncio asyncio REPL 3.10.6 (main, May 29 2023, 11:10:38) [GCC 11.3.0] on linux Use "await" directly instead of "asyncio.run()". Type "help", "copyright", "credits" or "license" for more information. >>> import asyncio >>> from bleak import BleakScanner >>> devices = await BleakScanner.discover() >>> [device.name for device in devices] ['Qingping Alarm Clock', 'abeacon_AC7D', 'TP358 (52C6)', '1B-0F-09-4F-89-F3', 'ThermoBeacon', 'F9-DA-D2-0D-62-24', '6A-C8-79-F4-E1-E5', 'Qingping BT Clock Lite', 'LYWSD02', 'Ruuvi 7E0E', 'TY', 'TP393 (2A3D)', 'Flower care', '52-9E-F1-64-DB-DF']
Give this top-level await approach a try with some of your favorite asynchronous Python libraries. You'll definitely become more productive in the REPL.