Item #61: Know how to port threaded I/O to asyncio.
- Python provides asynchronous versions of for loops, with statements, generators, comprehensions, and library helper functions that can be used as drop-in replacements in coroutines.
- The asyncio built-in module makes it straightforward to port existing code that uses threads and blocking I/O over to coroutines and asynchronous I/O.
Item #62: Mix threads and coroutines to ease the transition to asyncio.
- The awaitable run_in_executor method of the asyncio event loop enables coroutines to run synchronous functions in ThreadPoolExecutor pools. This facilitates top-down migrations to asyncio.
- The run_until_complete method of the asyncio event loop enables synchronous code to run a coroutine until it finishes. The asyncio.run_coroutine_threadsafe function provides the same functionality across thread boundaries. Together these help with bottom-up migrations to asyncio.
Item #63: Avoid blocking the asyncio event loop to maximize responsiveness.
- Making system calls in coroutines—including blocking I/O and starting threads—can reduce program responsiveness and increase the perception of latency.
- Pass the debug=True parameter to asyncio.run in order to detect when certain coroutines are preventing the event loop from reacting quickly.
Item #64: Consider concurrent.futures for true parallelism.
- Moving CPU bottlenecks to C-extension modules can be an effective way to improve performance while maximizing your investment in Python code. However, doing so has a high cost and may introduce bugs.
- The multiprocessing module provides powerful tools that can parallelize certain types of Python computation with minimal effort.
- The power of multiprocessing is best accessed through the concurrent.futures built-in module and its simple ProcessPoolExecutor class.
- Avoid the advanced (and complicated) parts of the multiprocessing module until you've exhausted all other options.
Item #65: Take advantage of each block in try, except, else, and finally.
- The try/finally compound statement lets you run cleanup code regardless of whether exceptions were raised in the try block.
- The else block helps you minimize the amount of code in try blocks and visually distinguish the success case from the try/except blocks.
- An else block can be used to perform additional actions after a successful try block but before common cleanup in a finally block.
import json
try:
my_file = open('my_file.json')
except FileNotFoundError as e:
print(e)
else:
try:
data = json.loads(my_file.read())
except json.decoder.JSONDecodeError as e:
print(e)
else:
# Work with data here.
pass
finally:
my_file.close()