Het pad naar het typechecken van 4 miljoen regels Python-code. Deel 1

Vandaag brengen we het eerste deel van de vertaling van het materiaal onder uw aandacht over hoe Dropbox omgaat met typecontrole van Python-code.

Het pad naar het typechecken van 4 miljoen regels Python-code. Deel 1

Dropbox schrijft veel in Python. Het is een taal die we extreem veel gebruiken, zowel voor back-endservices als voor desktopclienttoepassingen. We gebruiken ook veel Go, TypeScript en Rust, maar Python is onze hoofdtaal. Gezien onze schaal, en we hebben het over miljoenen regels Python-code, bleek dat het dynamisch typen van dergelijke code het begrip ervan onnodig ingewikkeld maakte en de arbeidsproductiviteit ernstig begon te beïnvloeden. Om dit probleem te verhelpen, zijn we begonnen onze code geleidelijk over te zetten naar statische typecontrole met behulp van mypy. Dit is waarschijnlijk het meest populaire op zichzelf staande controlesysteem voor Python. Mypy is een open source-project, de belangrijkste ontwikkelaars werken in Dropbox.

Dropbox was een van de eerste bedrijven die op deze schaal statische typecontrole in Python-code implementeerde. Mypy wordt tegenwoordig in duizenden projecten gebruikt. Deze tool is ontelbare keren, zoals ze zeggen, 'getest in de strijd'. We hebben een lange weg afgelegd om te komen waar we nu zijn. Onderweg waren er veel mislukte ondernemingen en mislukte experimenten. Dit bericht behandelt de geschiedenis van statische typecontrole in Python, vanaf het rotsachtige begin als onderdeel van mijn onderzoeksproject tot de dag van vandaag, wanneer typecontrole en typehints gemeengoed zijn geworden voor talloze ontwikkelaars die in Python schrijven. Deze mechanismen worden nu ondersteund door veel tools zoals IDE's en code-analyzers.

Lees het tweede deel

Waarom is typecontrole nodig?

Als je ooit dynamisch getypte Python hebt gebruikt, heb je misschien enige verwarring over waarom er de laatste tijd zoveel ophef is geweest over statisch typen en mypy. Of misschien vind je Python juist leuk vanwege het dynamische typen, en wat er gebeurt, maakt je gewoon van streek. De sleutel tot de waarde van statisch typen is de schaal van de oplossingen: hoe groter uw project, hoe meer u neigt naar statisch typen en hoe meer u het uiteindelijk echt nodig heeft.

Stel dat een bepaald project de grootte van tienduizenden regels heeft bereikt en blijkt dat er meerdere programmeurs aan werken. Als we naar een soortgelijk project kijken, kunnen we op basis van onze ervaring zeggen dat het begrijpen van de code de sleutel zal zijn om ontwikkelaars productief te houden. Zonder type-annotaties kan het moeilijk zijn om bijvoorbeeld uit te zoeken welke argumenten aan een functie moeten worden doorgegeven, of welke typen een functie kan retourneren. Hier zijn typische vragen die vaak moeilijk te beantwoorden zijn zonder typeannotaties te gebruiken:

  • Kan deze functie terugkeren None?
  • Wat zou dit argument moeten zijn? items?
  • Wat is het attribuuttype id: int of het str, of misschien een aangepast type?
  • Moet dit argument een lijst zijn? Is het mogelijk om er een tuple aan door te geven?

Als je naar het volgende codefragment met type-annotaties kijkt en vergelijkbare vragen probeert te beantwoorden, blijkt dat dit de eenvoudigste taak is:

class Resource:
    id: bytes
    ...
    def read_metadata(self, 
                      items: Sequence[str]) -> Dict[str, MetadataItem]:
        ...

  • read_metadata keert niet terug None, aangezien het retourtype dat niet is Optional[…].
  • argument items is een opeenvolging van lijnen. Het kan niet willekeurig herhaald worden.
  • Attribuut id is een reeks bytes.

In een ideale wereld zou men verwachten dat al dergelijke subtiliteiten worden beschreven in de ingebouwde documentatie (docstring). Maar de ervaring geeft veel voorbeelden van het feit dat dergelijke documentatie vaak niet wordt nageleefd in de code waarmee je moet werken. Zelfs als dergelijke documentatie in de code aanwezig is, kan men niet rekenen op de absolute juistheid ervan. Deze documentatie kan vaag, onnauwkeurig en vatbaar voor misverstanden zijn. In grote teams of grote projecten kan dit probleem extreem acuut worden.

Hoewel Python uitblinkt in de vroege of tussenliggende stadia van projecten, kunnen succesvolle projecten en bedrijven die Python gebruiken op een gegeven moment voor de cruciale vraag komen te staan: "Moeten we alles herschrijven in een statisch getypeerde taal?".

Typecontrolesystemen zoals mypy lossen het bovenstaande probleem op door de ontwikkelaar een formele taal te bieden voor het beschrijven van typen, en door te controleren of typedeclaraties overeenkomen met de programma-implementatie (en, optioneel, door te controleren op hun bestaan). Over het algemeen kunnen we zeggen dat deze systemen ons zoiets als zorgvuldig gecontroleerde documentatie ter beschikking stellen.

Het gebruik van dergelijke systemen heeft nog andere voordelen, en ze zijn al helemaal niet triviaal:

  • Het typecontrolesysteem kan enkele kleine (en niet zo kleine) fouten detecteren. Een typisch voorbeeld is wanneer ze vergeten een waarde te verwerken None of een andere bijzondere voorwaarde.
  • Code-refactoring is aanzienlijk vereenvoudigd omdat het typecontrolesysteem vaak zeer nauwkeurig aangeeft welke code moet worden gewijzigd. Tegelijkertijd hoeven we bij testen niet te hopen op 100% codedekking, wat sowieso meestal niet haalbaar is. We hoeven niet diep in de stacktracering te duiken om de oorzaak van het probleem te achterhalen.
  • Zelfs bij grote projecten kan mypy vaak in een fractie van een seconde een volledige typecontrole uitvoeren. En het uitvoeren van tests duurt meestal tientallen seconden of zelfs minuten. Het typecontrolesysteem geeft de programmeur direct feedback en stelt hem in staat zijn werk sneller te doen. Hij hoeft niet langer fragiele en moeilijk te onderhouden unit-tests te schrijven die echte entiteiten vervangen door mocks en patches om sneller codetestresultaten te krijgen.

IDE's en editors zoals PyCharm of Visual Studio Code gebruiken de kracht van type-annotaties om ontwikkelaars codeaanvulling, foutmarkering en ondersteuning voor veelgebruikte taalconstructies te bieden. En dit zijn slechts enkele van de voordelen van typen. Voor sommige programmeurs is dit alles het belangrijkste argument voor typen. Dat is iets waar je direct na implementatie profijt van hebt. Deze use case voor typen vereist geen apart typecontrolesysteem zoals mypy, hoewel moet worden opgemerkt dat mypy helpt om type-annotaties consistent te houden met code.

Achtergrond van mypy

De geschiedenis van mypy begon in het Verenigd Koninkrijk, in Cambridge, een paar jaar voordat ik bij Dropbox kwam. Als onderdeel van mijn doctoraatsonderzoek ben ik betrokken geweest bij de unificatie van statisch getypeerde en dynamische talen. Ik werd geïnspireerd door een artikel over incrementeel typen door Jeremy Siek en Walid Taha, en door het Typed Racket-project. Ik probeerde manieren te vinden om dezelfde programmeertaal voor verschillende projecten te gebruiken - van kleine scripts tot codebases die uit vele miljoenen regels bestaan. Tegelijkertijd wilde ik ervoor zorgen dat men bij een project van welke omvang dan ook niet al te grote compromissen zou moeten sluiten. Een belangrijk onderdeel van dit alles was het idee om geleidelijk over te gaan van een niet-getypeerd prototypeproject naar een uitgebreid getest statisch getypt eindproduct. Tegenwoordig worden deze ideeën grotendeels als vanzelfsprekend beschouwd, maar in 2010 was het een probleem dat nog actief werd onderzocht.

Mijn oorspronkelijke werk in typecontrole was niet gericht op Python. In plaats daarvan gebruikte ik een kleine "zelfgemaakte" taal Alore. Hier is een voorbeeld dat u zal laten begrijpen waar we het over hebben (type annotaties zijn hier optioneel):

def Fib(n as Int) as Int
  if n <= 1
    return n
  else
    return Fib(n - 1) + Fib(n - 2)
  end
end

Het gebruik van een vereenvoudigde moedertaal is een gebruikelijke aanpak in wetenschappelijk onderzoek. Dit is niet in de laatste plaats omdat het je in staat stelt om snel experimenten uit te voeren, en ook omdat wat niets met het onderzoek te maken heeft, gemakkelijk kan worden genegeerd. Real-world programmeertalen zijn meestal grootschalige fenomenen met complexe implementaties, en dit vertraagt ​​het experimenteren. Alle resultaten op basis van een vereenvoudigde taal zien er echter een beetje verdacht uit, aangezien de onderzoeker bij het verkrijgen van deze resultaten mogelijk belangrijke overwegingen voor het praktische gebruik van talen heeft opgeofferd.

Mijn typechecker voor Alore zag er veelbelovend uit, maar ik wilde het testen door te experimenteren met echte code, die, zou je kunnen zeggen, niet in Alore is geschreven. Gelukkig voor mij was de Alore-taal grotendeels gebaseerd op dezelfde ideeën als Python. Het was eenvoudig genoeg om de typechecker opnieuw te maken, zodat deze kon werken met de syntaxis en semantiek van Python. Hierdoor konden we proberen typecontrole uit te voeren in open source Python-code. Daarnaast heb ik een transpiler geschreven om in Alore geschreven code om te zetten in Python-code en deze gebruikt om mijn typechecker-code te vertalen. Nu had ik een typecontrolesysteem geschreven in Python dat een subset van Python ondersteunde, een soort van die taal! (Bepaalde architecturale beslissingen die logisch waren voor Alore, waren slecht geschikt voor Python, en dit is nog steeds merkbaar in delen van de mypy-codebase.)

In feite kon de taal die door mijn typesysteem werd ondersteund op dit moment nog niet helemaal Python worden genoemd: het was een variant van Python vanwege enkele beperkingen van de annotatiesyntaxis van het Python 3-type.

Het zag eruit als een mix van Java en Python:

int fib(int n):
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)

Een van mijn ideeën destijds was om type-annotaties te gebruiken om de prestaties te verbeteren door dit soort Python naar C te compileren, of misschien JVM-bytecode. Ik kwam in de fase van het schrijven van een compiler-prototype, maar ik liet dit idee varen, omdat typecontrole op zich best nuttig leek.

Uiteindelijk presenteerde ik mijn project op PyCon 2013 in Santa Clara. Ik sprak er ook over met Guido van Rossum, de welwillende Python-dictator voor het leven. Hij overtuigde me om mijn eigen syntaxis te laten vallen en vast te houden aan de standaard syntaxis van Python 3. Python 3 ondersteunt functieannotaties, dus mijn voorbeeld kan worden herschreven zoals hieronder wordt weergegeven, wat resulteert in een normaal Python-programma:

def fib(n: int) -> int:
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)

Ik moest een aantal compromissen sluiten (ik wil allereerst opmerken dat ik om deze reden mijn eigen syntaxis heb uitgevonden). Met name Python 3.3, destijds de meest recente versie van de taal, ondersteunde geen variabele annotaties. Ik besprak met Guido per e-mail verschillende mogelijkheden voor syntactisch ontwerp van dergelijke annotaties. We hebben besloten om typeopmerkingen voor variabelen te gebruiken. Dit diende het beoogde doel, maar was enigszins omslachtig (Python 3.6 gaf ons een mooiere syntaxis):

products = []  # type: List[str]  # Eww

Type-opmerkingen waren ook handig om Python 2 te ondersteunen, dat geen ingebouwde ondersteuning voor type-annotaties heeft:

f fib(n):
    # type: (int) -> int
    if n <= 1:
        return n
    else:
        return fib(n - 1) + fib(n - 2)

Het bleek dat deze (en andere) compromissen er niet echt toe deden - de voordelen van statisch typen zorgden ervoor dat gebruikers snel de niet-perfecte syntaxis vergaten. Omdat er geen speciale syntactische constructies werden gebruikt in de typegecontroleerde Python-code, bleven bestaande Python-tools en codeverwerkingsprocessen normaal werken, waardoor het voor ontwikkelaars veel gemakkelijker werd om de nieuwe tool te leren kennen.

Guido overtuigde me ook om lid te worden van Dropbox nadat ik mijn afstudeerscriptie had afgerond. Dit is waar het meest interessante deel van het mypy-verhaal begint.

Wordt vervolgd ...

Beste lezers! Als u Python gebruikt, vertel ons dan alstublieft over de omvang van de projecten die u in deze taal ontwikkelt.

Het pad naar het typechecken van 4 miljoen regels Python-code. Deel 1
Het pad naar het typechecken van 4 miljoen regels Python-code. Deel 1

Bron: www.habr.com

Voeg een reactie