Effective Python 61 - 65

Click here for the first post, which contains the context of this series.

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()