3 passaggi per una revisione asincrona di Python

Python è uno dei tanti linguaggi che supportano in qualche modo la scrittura di programmi asincroni - programmi che passano liberamente tra più attività, tutte in esecuzione contemporaneamente, in modo che nessuna attività blocchi il progresso delle altre.

È probabile, tuttavia, che tu abbia scritto principalmente programmi Python sincroni, programmi che fanno solo una cosa alla volta, aspettando che ogni attività finisca prima di avviarne un'altra. Passare all'asincronia può essere stridente, poiché richiede non solo l'apprendimento di una nuova sintassi, ma anche di nuovi modi di pensare al proprio codice. 

In questo articolo, esploreremo come un programma sincrono esistente può essere trasformato in uno asincrono. Ciò implica più della semplice decorazione di funzioni con sintassi asincrona; richiede anche di pensare in modo diverso a come viene eseguito il nostro programma e di decidere se async è anche una buona metafora di ciò che fa. 

[Anche su: Impara trucchi e suggerimenti su Python dai video Smart Python di Serdar Yegulalp]

Quando usare async in Python

Un programma Python è più adatto per l'asincronia quando ha le seguenti caratteristiche:

  • Sta cercando di fare qualcosa che è per lo più vincolato dall'I / O o dall'attesa del completamento di un processo esterno, come una lettura di rete di lunga durata.
  • Sta cercando di eseguire uno o più di questi tipi di attività contemporaneamente, possibilmente gestendo anche le interazioni dell'utente.
  • I compiti in questione non sono pesanti dal punto di vista computazionale.

Un programma Python che utilizza il threading è in genere un buon candidato per l'utilizzo di async. I thread in Python sono cooperativi; si cedono l'un l'altro secondo necessità. Le attività asincrone in Python funzionano allo stesso modo. Inoltre, async offre alcuni vantaggi rispetto ai thread:

  • La sintassi async/ awaitsemplifica l'identificazione delle parti asincrone del programma. Al contrario, è spesso difficile dire a colpo d'occhio quali parti di un'app vengono eseguite in un thread. 
  • Poiché le attività asincrone condividono lo stesso thread, tutti i dati a cui accedono vengono gestiti automaticamente dal GIL (il meccanismo nativo di Python per la sincronizzazione dell'accesso agli oggetti). I thread richiedono spesso meccanismi complessi per la sincronizzazione. 
  • Le attività asincrone sono più facili da gestire e annullare rispetto ai thread.

L'uso di async non è consigliato se il tuo programma Python ha queste caratteristiche:

  • Le attività hanno un costo computazionale elevato, ad esempio eseguono pesanti operazioni di elaborazione dei numeri. Il lavoro di calcolo pesante viene gestito al meglio multiprocessing, il che consente di dedicare un intero thread hardware a ciascuna attività.
  • Le attività non traggono vantaggio dall'essere interfogliate. Se ogni attività dipende dall'ultima, non ha senso farle funzionare in modo asincrono. Detto questo, se il programma coinvolge  set di attività seriali, è possibile eseguire ogni set in modo asincrono.

Passaggio 1: identificare le parti sincrone e asincrone del programma

Il codice asincrono Python deve essere avviato e gestito dalle parti sincrone della tua applicazione Python. A tal fine, la prima attività durante la conversione di un programma in asincrono è tracciare una linea tra le parti di sincronizzazione e asincrone del codice.

Nel nostro precedente articolo sull'asincronia, abbiamo utilizzato un'app di web scraper come semplice esempio. Le parti asincrone del codice sono le routine che aprono le connessioni di rete e leggono dal sito - tutto ciò che vuoi intercalare. Ma la parte del programma che dà il via a tutto ciò non è asincrona; avvia le attività asincrone e quindi le chiude con grazia man mano che finiscono.

È anche importante separare qualsiasi operazione di blocco potenziale  dall'asincronia e mantenerla nella parte di sincronizzazione della tua app. La lettura dell'input dell'utente dalla console, ad esempio, blocca tutto, incluso il ciclo di eventi asincrono. Pertanto, si desidera gestire l'input dell'utente prima di avviare le attività asincrone o dopo averle completate. (E è possibile l'input dell'utente maniglia in modo asincrono tramite multiprocessing o threading, ma questo è un esercizio avanzato noi non entreremo in qui.)

Alcuni esempi di operazioni di blocco:

  • Input da console (come abbiamo appena descritto).
  • Attività che comportano un utilizzo intenso della CPU.
  • Usando time.sleepper forzare una pausa. Nota che puoi dormire all'interno di una funzione asincrona usando asyncio.sleepcome sostituto di time.sleep.

Passaggio 2: converti le funzioni di sincronizzazione appropriate in funzioni asincrone

Una volta che sai quali parti del tuo programma verranno eseguite in modo asincrono, puoi suddividerle in funzioni (se non lo hai già fatto) e trasformarle in funzioni asincrone con la asyncparola chiave. Dovrai quindi aggiungere codice alla parte sincrona dell'applicazione per eseguire il codice asincrono e raccogliere i risultati da esso, se necessario.

Nota: ti consigliamo di controllare la catena di chiamate di ogni funzione che hai reso asincrono e assicurarti che non stiano invocando un'operazione potenzialmente di lunga durata o di blocco. Le funzioni asincrone possono chiamare direttamente le funzioni di sincronizzazione e, se la funzione di sincronizzazione si blocca, lo stesso vale per la funzione asincrona che la chiama.

Diamo un'occhiata a un esempio semplificato di come potrebbe funzionare una conversione da sincronizzazione a asincrona. Ecco il nostro programma "prima":

def a_function (): # alcune azioni compatibili con l'asincronia che richiedono un po 'di tempo def another_function (): # alcune funzioni di sincronizzazione, ma non un blocco def do_stuff (): a_function () another_function () def main (): for _ nell'intervallo (3): do_stuff () main () 

Se vogliamo che tre istanze di do_stuffvengano eseguite come attività asincrone, dobbiamo trasformare do_stuff (e potenzialmente tutto ciò che tocca) in codice asincrono. Ecco un primo passaggio alla conversione:

import asyncio async def a_function (): # alcune azioni compatibili con l'asincronia che richiedono un po 'def another_function (): # alcune funzioni di sincronizzazione, ma non un blocco asincrono def do_stuff (): await a_function () another_function () async def main ( ): tasks = [] for _ in range (3): tasks.append (asyncio.create_task (do_stuff ())) await asyncio.gather (tasks) asyncio.run (main ()) 

Nota le modifiche che abbiamo apportato a  main. Ora main utilizza asyncioper avviare ogni istanza di do_stuffcome attività simultanea, quindi attende i risultati ( asyncio.gather). Abbiamo anche convertito a_functionin una funzione asincrona, poiché vogliamo che tutte le istanze di vengano a_functioneseguite fianco a fianco e insieme a qualsiasi altra funzione che richiede un comportamento asincrono.

Se volessimo fare un ulteriore passo avanti, potremmo anche convertire another_functionin asincrono:

async def another_function (): # alcune funzioni di sincronizzazione, ma non il blocco di una async def do_stuff (): await a_function () wait another_function () 

Tuttavia, rendere  another_function asincrono sarebbe eccessivo, poiché (come abbiamo notato) non fa nulla che possa bloccare il progresso del nostro programma. Inoltre, se vengono chiamate parti sincrone del nostro programma  another_function, dovremmo convertirle anche in asincrone, il che potrebbe rendere il nostro programma più complicato di quanto dovrebbe essere.

Passaggio 3: verifica accuratamente il tuo programma asincrono Python

Qualsiasi programma convertito in modo asincrono deve essere testato prima di entrare in produzione per assicurarsi che funzioni come previsto.

Se il tuo programma è di dimensioni modeste, ad esempio un paio di dozzine di righe o giù di lì, e non necessita di una suite di test completa, non dovrebbe essere difficile verificare che funzioni come previsto. Detto questo, se stai convertendo il programma in asincrono come parte di un progetto più ampio, in cui una suite di test è un dispositivo standard, ha senso scrivere unit test per i componenti asincroni e sincronizzati allo stesso modo.

Entrambi i principali framework di test in Python ora dispongono di una sorta di supporto asincrono. Il unittest framework di Python  include oggetti test case per funzioni asincrone e pytestofferte  pytest-asyncioper gli stessi scopi.

Finally, when writing tests for async components, you’ll need to handle their very asynchronousness as a condition of the tests. For instance, there is no guarantee that async jobs will complete in the order they were submitted. The first one might come in last, and some might never complete at all. Any tests you design for an async function must take these possibilities into account.

How to do more with Python

  • Get started with async in Python
  • How to use asyncio in Python
  • How to use PyInstaller to create Python executables
  • Cython tutorial: How to speed up Python
  • How to install Python the smart way
  • How to manage Python projects with Poetry
  • How to manage Python projects with Pipenv
  • Virtualenv and venv: Python virtual environments explained
  • Python virtualenv and venv do’s and don’ts
  • Python threading and subprocesses explained
  • How to use the Python debugger
  • Come usare timeit per profilare il codice Python
  • Come usare cProfile per profilare il codice Python
  • Come convertire Python in JavaScript (e viceversa)