INNOVATIEPYTHONSOFTWARE DEVELOPMENT
01/02/2021 • Stijn Schutyser

FastAPI: de Python-oplossing voor hoogperformante REST API's

Python is het gespreksonderwerp van vandaag. Het is dan ook uitgegroeid tot de tweede meest gebruikte programmeertaal en moet enkel JavaScript laten voorgaan. Python heeft die zilveren medaille hoofdzakelijk te danken aan het doorgedreven gebruik van AI en de analyse van (big) data. Maar terwijl AI en data-analyse vrij nieuwe domeinen zijn, bestaat Python al meer dan 30 jaar. Het werd voor het eerst gelanceerd op 20 februari 1991.

Python dankt veel van zijn ontwikkeling en groei aan Google, dat al gretig gebruik maakte van de taal. Meer nog, ze namen zelfs een hele tijd de Nederlander Guido Van Rossum (de oprichter van Python) in dienst.

overzicht ranking van programmeertalen sinds 2018

“Python als het kan, C als het moet”, zo luidt het motto van Google al jarenlang. De reden daarvoor ligt nogal voor de hand: de voordelen van Python zijn overduidelijk. Het is makkelijk aan te leren en het heeft een heldere en korte syntaxis. Dat betekent dat de code kort en leesbaar is, en snel kan worden geschreven. De tijdsbesparing bij het schrijven van de code is één van de belangrijkste troeven van Python.

Waarvoor wordt Python gebruikt?

Zoals gezegd, wordt Python vaak gebruikt in AI en datawetenschap. Daarnaast is het ook de aangewezen keuze voor DevOps en taakautomatisering. Op het vlak van webontwikkeling daarentegen had Python toch heel wat tijd nodig om een voor de hand liggend alternatief te worden. De voornaamste reden hiervoor is wellicht dat er in dit domein veel concurrentie is van talen zoals Java, PHP, Dotnet en NodeJS (= JavaScript-framework), waarbij PHP en NodeJS uitsluitend werden ontwikkeld voor websites. Er is bovendien nog een andere belangrijke reden, die we later in dit artikel zullen bespreken.

Python is een geïnterpreteerde taal

Python is net als JavaScript een geïnterpreteerde taal. Het is met andere woorden niet rechtstreeks bruikbaar door een computer, maar heeft een ander programma nodig om de code uit te voeren. Het voordeel hiervan is dat er niet gecompileerd moet worden, wat dan weer tijd bespaart. Er is echter ook een keerzijde: een geïnterpreteerde taal is veel trager dan een gecompileerde taal. 

Er zijn Python-implementaties zoals Cython en Jython, die een Python-code omzetten naar bytecode of C-extensies. Daar gaan we het in dit artikel niet over hebben. 

Hoewel Python en JavaScript geïnterpreteerde talen zijn, is (was) Python meestal het traagst. Een van de redenen hiervoor is dat NodeJS gebouwd werd voor het web, over geavanceerde multithreading-mogelijkheden beschikt en eventgedreven is. Python daarentegen bestaat al even lang als het internet (het werd voor het eerst gelanceerd in 1991) en had andere doeleinden. Multithreading is niet ingebouwd in Python, maar het is beschikbaar in de toolbox.

Python in een stroomversnelling brengen met concurrency

Bij de ontwikkeling van websites of API's in Python zijn er heel wat frameworks om uit te kiezen. De populairste frameworks zijn Flask en Django. Heb je echter alleen een API nodig voor je fraaie React- of Angular-website, dan bestaat er een veel beter alternatief: FastAPI. FastAPI – dat momenteel aan versie 0.70.0 zit – wint snel aan populariteit. Dat komt onder andere omdat het doet wat het zegt: het is snel. Niet omdat het licht is, maar omdat het out-of-the-box ondersteuning biedt voor concurrency (asynchrone functies, vereist Python 3.6+).

Gelijktijdigheid (of concurrency in het Engels) maakt alles sneller. Het draait allemaal om de optimalisatie van CPU- of I/O-gebaseerde taken, waarbij die laatste je code het meest vertragen. Hangt je API bijvoorbeeld af van een andere API om extra gegevens te verzamelen, dan zal het HTTP-verzoek heel wat tijd in beslag nemen (in vergelijking met CPU-taken). Als je meerdere verzoeken hebt, kan dat je code aanzienlijk vertragen.

Laten we er een voorbeeld bij nemen. Het onderstaande script zal 50 websites downloaden zonder concurrency (synchroon).

import requests
import time

def download(url, session):
   with session.get(url) as response:
       print(f"Read {len(response.content)} from {url}")

def download_all(sites):
   with requests.Session() as session:
       for url in sites:
           download(url, session)

if __name__ == "__main__":
   sites = [
       "https://www.python.org",
       "https://www.jython.org",
       "https://pypi.org/",
       "https://fastapi.tiangolo.com/",
       "https://flask.palletsprojects.com/en/2.0.x/"
   ] * 10
   start_time = time.time()
   download_all(sites)
   duration = time.time() - start_time
   print(f"Downloaded {len(sites)} in {duration} seconds")

Als we dit script uitvoeren, is dit het resultaat:

… output skipped …
Read 41381 from https://flask.palletsprojects.com/en/2.0.x/
Downloaded 50 sites in 5.598579168319702 seconds

5 seconden om 50 websites te downloaden is niet slecht, maar het kan veel beter.

Om het tempo op te drijven, kunnen we kiezen uit twee manieren waarop Python kan omgaan met gelijktijdigheid: threading en asyncio. Threading werd geïntroduceerd in Python 3.2 en werkt prima. Toch zijn er ook enkele nadelen. Threads kunnen op elkaar inwerken op manieren die erg subtiel en moeilijk te detecteren zijn. Je zult moeten experimenteren met het aantal threads, omdat het aantal varieert afhankelijk van wat je wilt bereiken. Een nog betere manier om alles te versnellen, is asyncio (geïntroduceerd in Python 3.6). Asyncio geeft je meer controle en is eenvoudiger te implementeren dan threading. Dezelfde code herschreven voor asyncio zou er als volgt uitzien:

import asyncio
import time
import aiohttp

async def download(session, url):
   async with session.get(url) as response:
       print("Read {0} from {1}".format(response.content_length, url))

async def download_all(sites):
   async with aiohttp.ClientSession() as session:
       tasks = []
       for url in sites:
           task = asyncio.ensure_future(download(session, url))
           tasks.append(task)
       await asyncio.gather(*tasks, return_exceptions=True)

if __name__ == "__main__":
   sites = [
       "https://www.python.org",
       "https://www.jython.org",
       "https://pypi.org/",
       "https://fastapi.tiangolo.com/",
       "https://flask.palletsprojects.com/en/2.0.x/"
   ] * 10
   start_time = time.time()
   asyncio.get_event_loop().run_until_complete(download_all(sites))
   duration = time.time() - start_time
   print(f"Downloaded {len(sites)} sites in {duration} seconds")

Als we dit script uitvoeren, is dit het resultaat:

… output skipped …
Read 41381 from https://flask.palletsprojects.com/en/2.0.x/
Downloaded 50 sites in 1.0634150505065918 seconds

Dezelfde 50 websites worden gedownload in 1 seconde, ofwel 5 keer sneller dan met de synchrone code!

In benchmarks met NodeJS is FastAPI in bijna alle gevallen sneller. Doe daar nog de ontwikkelsnelheid van Python, de talloze Python-bibliotheken die beschikbaar zijn op het web, de ingebouwde OpenAPI (Swagger)-documentatie en het volledig uitgeruste testsysteem bij en de keuze is snel gemaakt.

Fast API overview

Wil je je Python-ontwikkeling ook in een stroomversnelling brengen? Contacteer ons om te ontdekken hoe wij je daarbij kunnen helpen!

Leuk weetje: er is zelfs Python op Mars

foto van de Perseverance op Mars

Wist je dat NASA Python gebruikt heeft tijdens de Marsmissie met de Perseverance? Het moment van de landing zelf werd opgenomen door 5 camera's die in verschillende delen van de ruimtesonde werden geplaatst. Er werd gebruik gemaakt van Python-scripts om de beelden te verwerken en door te sturen naar het vluchtleidingscentrum. En als NASA een programmeertaal gebruikt, dan moet die wel betrouwbaar zijn. 😉

P.S. We organiseren een Python Bootcamp om je te helpen specialiseren in Python in slechts 3 dagen! Interesse? Klik op het linkje hieronder!

Stijn Schutyser