Programmering

3 trin til en Python async-eftersyn

Python er en af ​​mange sprog, der understøtter en måde at skrive asynkrone programmer på - programmer, der skifter frit mellem flere opgaver, som alle kører på én gang, så ingen opgave holder de andres fremskridt.

Chancerne er dog, at du hovedsageligt har skrevet synkrone Python-programmer - programmer, der kun gør en ting ad gangen og venter på, at hver opgave er færdig, før du starter en anden. At gå til asynkronisering kan være skurrende, da det kræver ikke kun at lære ny syntaks, men også nye måder at tænke på ens kode på.

I denne artikel undersøger vi, hvordan et eksisterende, synkron program kan omdannes til et asynkront program. Dette involverer mere end bare dekorering af funktioner med asynk-syntaks; det kræver også at tænke anderledes om, hvordan vores program kører, og beslutte, om async endda er en god metafor for, hvad det gør.

[Også på: Lær Python-tip og tricks fra Serdar Yegulalps Smart Python-videoer]

Hvornår skal man bruge asynkronisering i Python

Et Python-program er bedst egnet til asynkronisering, når det har følgende egenskaber:

  • Det forsøger at gøre noget, der for det meste er bundet af I / O eller ved at vente på, at en eller anden ekstern proces skal gennemføres, som en langvarig netværkslæsning.
  • Det forsøger at udføre en eller flere af disse slags opgaver på én gang, samtidig med at det muligvis også håndterer brugerinteraktioner.
  • De pågældende opgaver er ikke tunge.

Et Python-program, der bruger threading, er typisk en god kandidat til at bruge async. Tråde i Python er samarbejdsvillige; de giver efter hinanden efter behov. Async-opgaver i Python fungerer på samme måde. Plus, async tilbyder visse fordele i forhold til tråde:

  • Det asynkronisering/vente syntaks gør det let at identificere de asynkrone dele af dit program. Derimod er det ofte svært at fortælle et øjeblik hvilke dele af en app, der kører i en tråd.
  • Fordi asynkroniseringsopgaver deler den samme tråd, administreres alle data, de får adgang til automatisk af GIL (Pythons oprindelige mekanisme til synkronisering af adgang til objekter). Tråde kræver ofte komplekse mekanismer til synkronisering.
  • Async-opgaver er lettere at administrere og annullere end tråde.

Brug af async er ikke anbefales, hvis dit Python-program har disse egenskaber:

  • Opgaverne har høje beregningsomkostninger - for eksempel laver de tunge tal. Tungt beregningsarbejde håndteres bedst med multiprocessing, som giver dig mulighed for at afsætte en hel hardware tråd til hver opgave.
  • Opgaverne drager ikke fordel af at blive sammenflettet. Hvis hver opgave afhænger af den sidste, er der ingen mening at få dem til at køre asynkront. Når det er sagt, hvis programmet involverersæt af serielle opgaver, kan du køre hvert sæt asynkront.

Trin 1: Identificer de synkrone og asynkrone dele af dit program

Python async-kode skal startes af og styres af de synkrone dele af din Python-applikation. Til dette formål er din første opgave, når du konverterer et program til asynkronisering, at tegne en linje mellem synkroniserings- og asynkroniseringsdelene i din kode.

I vores tidligere artikel om async brugte vi en webskraber-app som et simpelt eksempel. De asynkroniserede dele af koden er de rutiner, der åbner netværksforbindelserne og læser fra webstedet - alt, hvad du vil blande. Men den del af programmet, der starter alt det, er ikke asynkroniseret; det starter asynkroniseringsopgaverne og lukker dem derefter yndefuldt, når de er færdige.

Det er også vigtigt at adskille potentieltblokering fra async, og hold det i synkroniseringsdelen af ​​din app. Læsning af brugerinput fra konsollen blokerer for eksempel alt inklusive async-hændelsesløkken. Derfor vil du håndtere brugerinput enten før du starter asynkroniseringsopgaver eller efter at du er færdig med dem. (Det er muligt at håndtere brugerinput asynkront via multiprocessing eller threading, men det er en avanceret øvelse, vi ikke kommer ind på her.)

Nogle eksempler på blokeringsoperationer:

  • Konsolindgang (som vi netop har beskrevet).
  • Opgaver, der involverer tung CPU-udnyttelse.
  • Ved brug af tid. sove for at tvinge en pause. Bemærk, at du kan sove inde i en async-funktion ved hjælp af asyncio.sove som erstatning for tid. sove.

Trin 2: Konverter passende synkroniseringsfunktioner til asynkroniseringsfunktioner

Når du ved, hvilke dele af dit program, der kører asynkront, kan du opdele dem i funktioner (hvis du ikke allerede har gjort det) og gøre dem til asynkroniseringsfunktioner med asynkronisering nøgleord. Du bliver derefter nødt til at tilføje kode til den synkrone del af din applikation for at køre asynkroniseringskoden og om nødvendigt indsamle resultater fra den.

Bemærk: Du vil kontrollere opkaldskæden for hver funktion, du har foretaget asynkront, og sørge for, at de ikke påberåber sig en potentielt langvarig eller blokering. Async-funktioner kan direkte kalde synkroniseringsfunktioner, og hvis denne synkroniseringsfunktion blokeres, så kalder async-funktionen det også.

Lad os se på et forenklet eksempel på, hvordan en konvertering til synkronisering til asynkronisering muligvis fungerer. Her er vores "før" -program:

def a_function (): # nogle async-kompatible handlinger, der tager et stykke tid def another_function (): # nogle synkroniseringsfunktioner, men ikke en blokerende def def__stuff (): a_function () another_function () def main (): for _ inden for rækkevidde (3): do_stuff () main () 

Hvis vi vil have tre forekomster af gør_stuff for at køre som asynkroniseringsopgaver er vi nødt til at vende gør_stuff (og potentielt alt det rører ved) til asynkroniseringskode. Her er et første pass ved konverteringen:

importer asyncio async def a_function (): # noget async-kompatibel handling, der tager et stykke tid def another_function (): # nogle sync-funktion, men ikke en blokerende async def do_stuff (): afventer a_function () another_function () async def main ( ): opgaver = [] for _ i rækkevidde (3): opgaver.append (asyncio.create_task (do_stuff ())) afventer asyncio.gather (opgaver) asyncio.run (main ()) 

Bemærk de ændringer, vi har foretaget tilvigtigste. Nu vigtigste anvendelser asyncio for at starte hver forekomst af gør_stuff som en samtidig opgave, og venter derefter på resultaterne (asyncio.gather). Vi konverterede også a_funktion ind i en asynkroniseringsfunktion, da vi ønsker alle forekomster af a_funktion at løbe side om side og sammen med andre funktioner, der har brug for asynkroniseringsadfærd.

Hvis vi ønskede at gå et skridt videre, kunne vi også konvertere en anden_funktion at asynkronisere:

async def anden_funktion (): # nogle synkroniseringsfunktioner, men ikke en blokering en async def do_stuff (): afventer en_funktion () afventer en anden_funktion () 

Dog gøren anden_funktion asynkron ville være overkill, da det (som vi har bemærket) ikke gør noget, der kunne blokere for vores program. Også hvis der kaldes synkroniserede dele af vores programen anden_funktion, bliver vi også nødt til at konvertere dem til asynkronisering, hvilket kan gøre vores program mere kompliceret, end det skal være.

Trin 3: Test dit Python async-program grundigt

Ethvert asynk-konverteret program skal testes, før det går i produktion for at sikre, at det fungerer som forventet.

Hvis dit program er beskedent i størrelse - f.eks. Et par dusin linjer eller deromkring - og ikke har brug for en komplet testpakke, bør det ikke være svært at kontrollere, at det fungerer som beregnet. Når det er sagt, hvis du konverterer programmet til asynkronisering som en del af et større projekt, hvor en testpakke er en standardarmatur, er det fornuftigt at skrive enhedstest for både asynkroniserings- og synkroniseringskomponenter.

Begge de store testrammer i Python har nu en slags async-support. Pythons egenunittest framework inkluderer test case objekter til async funktioner, og pytest tilbudpytest-asyncio til de samme ender.

Endelig, når du skriver tests til asynkroniske komponenter, skal du håndtere deres meget asynkronitet som en betingelse for testene. F.eks. Er der ingen garanti for, at asynkroniseringsjob afsluttes i den rækkefølge, de blev indsendt. Den første kommer måske sidst ind, og nogle fuldender muligvis aldrig. Eventuelle tests, du designer til en async-funktion, skal tage disse muligheder i betragtning.

Sådan gør du mere med Python

  • Kom godt i gang med async i Python
  • Sådan bruges asyncio i Python
  • Sådan bruges PyInstaller til at oprette Python-eksekverbare filer
  • Cython tutorial: Sådan fremskyndes Python
  • Sådan installeres Python på den smarte måde
  • Sådan styres Python-projekter med Poetry
  • Sådan styres Python-projekter med Pipenv
  • Virtualenv og venv: Python virtuelle miljøer forklaret
  • Python virtualenv og venv do's and don'ts
  • Python-gevind og underprocesser forklaret
  • Sådan bruges Python debugger
  • Sådan bruges timeit til at profilere Python-kode
  • Sådan bruges cProfile til profilering af Python-kode
  • Sådan konverteres Python til JavaScript (og tilbage igen)