Heute präsentieren wir Ihnen den ersten Teil der Übersetzung des Materials darüber, wie bei Dropbox die Typüberprüfung von Python-Code gehandhabt wird.
Bei Dropbox wird viel in Python geschrieben. Dies ist eine Sprache, die wir äußerst umfangreich verwenden – sowohl für Backend-Dienste als auch für Desktop-Client-Anwendungen. Außerdem nutzen wir in großem Umfang Go, TypeScript und Rust, aber Python ist unsere Hauptsprache. Wenn man unsere Dimensionen bedenkt, sprechen wir von Millionen von Zeilen Python-Code, so stellte sich heraus, dass die dynamische Typisierung dieses Codes dessen Verständnis unnötig erschwerte und ernsthaft die Produktivität beeinträchtigte. Um diesem Problem entgegenzuwirken, begannen wir schrittweise damit, unseren Code mit mypy auf statische Typüberprüfung umzustellen. Dies ist wahrscheinlich das beliebteste autonome Typüberprüfungswerkzeug für Python. Mypy ist ein Open-Source-Projekt, dessen Hauptentwickler bei Dropbox tätig sind.
Dropbox war eines der ersten Unternehmen, das statische Typüberprüfung im Python-Code in einem solchen Umfang eingeführt hat. Heutzutage wird mypy in Tausenden von Projekten eingesetzt. Dieses Werkzeug wurde unzählige Male, sozusagen, „im Einsatz geprüft“. Um dorthin zu gelangen, wo wir jetzt sind, mussten wir einen langen Weg zurücklegen. Auf diesem Weg gab es zahlreiche gescheiterte Versuche und missratene Experimente. Dieses Material erzählt die Geschichte der statischen Typüberprüfung in Python – von ihrem anfänglichen, schwierigen Beginn, der Teil meines wissenschaftlichen Forschungsprojekts war, bis heute, als Typüberprüfungen und Typvorschläge für unzählige Entwickler, die in Python schreiben, zur Gewohnheit geworden sind. Diese Mechanismen werden mittlerweile von vielen Werkzeugen unterstützt – wie IDEs und Code-Analyse-Tools.
→
Warum ist die Typüberprüfung wichtig?
Wenn Sie jemals mit dem dynamisch typisierten Python gearbeitet haben, kann es sein, dass Sie nicht verstehen, warum in letzter Zeit so viel Aufregung um statische Typisierung und mypy herrscht. Vielleicht liegt es auch daran, dass Ihnen Python gerade wegen seiner dynamischen Typisierung gefällt und die aktuellen Entwicklungen Sie einfach frustrieren. Der Schlüssel zum Wert der statischen Typisierung liegt im Umfang der Lösungen: Je größer Ihr Projekt ist, desto mehr neigen Sie zur statischen Typisierung, und letztendlich brauchen Sie sie umso mehr.
Nehmen wir an, ein Projekt hat eine Größe von zehntausenden Zeilen erreicht und es arbeiten mehrere Programmierer daran. In Anbetracht eines solchen Projekts können wir basierend auf unserer Erfahrung sagen, dass das Verständnis des Codes der Schlüssel zur Aufrechterhaltung der Produktivität der Entwickler sein wird. Ohne Typannotationen ist es oft schwierig herauszufinden, welche Argumente an eine Funktion übergeben werden müssen oder welche Typen eine bestimmte Funktion zurückgeben kann. Hier sind typische Fragen, die ohne Typannotationen oft schwer zu beantworten sind:
- Kann diese Funktion zurückgeben
None? - Was sollte dieses Argument sein
items? - Was ist der Typ des Attributs
id:intist es,str, oder vielleicht ein benutzerdefinierter Typ? - Sollte dieses Argument eine Liste sein? Kann man ein Tuple dafür übergeben?
Wenn Sie den folgenden Codeabschnitt mit Typannotationen betrachten und versuchen, solche Fragen zu beantworten, werden Sie feststellen, dass dies eine sehr einfache Aufgabe ist:
class Resource:
id: bytes
...
def read_metadata(self,
items: Sequence[str]) -> Dict[str, MetadataItem]:
...read_metadatagibt nicht zurückNone, da der Rückgabetyp keinOptional[…].- Argument
itemsist eine Zeichenfolgenfolge. Man kann sie nicht in beliebiger Reihenfolge durchlaufen. - des Elements ist jetzt
idist eine Byte-Zeichenfolge.
In einer idealen Welt könnte man erwarten, dass alle derartigen Feinheiten in der eingebetteten Dokumentation (docstring) beschrieben werden. Doch die Erfahrung liefert zahlreiche Beispiele dafür, dass solche Dokumentation im Code, mit dem man arbeiten muss, oft nicht vorhanden ist. Selbst wenn solche Dokumentation im Code vorhanden ist, kann man sich nicht auf deren absolute Richtigkeit verlassen. Diese Dokumentation kann unklar, ungenau und missverständlich sein, was viele Möglichkeiten für falsches Verständnis eröffnet. In großen Teams oder Projekten kann dieses Problem äußerst akut werden.
Obwohl Python in den frühen oder mittleren Phasen von Projekten hervorragend abschneidet, können erfolgreiche Projekte und Unternehmen, die Python verwenden, irgendwann mit der entscheidenden Frage konfrontiert werden: "Müssen wir alles in einer statisch typisierten Sprache neu schreiben?".
Typprüfsysteme wie mypy lösen das oben genannte Problem, indem sie den Entwicklern eine formale Sprache zur Beschreibung von Typen zur Verfügung stellen und überprüfen, ob die Typbeschreibungen mit der Implementierung der Programme übereinstimmen (und optional überprüfen sie auch deren Existenz). Im Großen und Ganzen kann man sagen, dass diese Systeme uns eine Art gründlich geprüfte Dokumentation bieten.
Die Verwendung solcher Systeme hat auch andere Vorteile, die durchaus nicht trivial sind:
- Das Typprüfsystem kann einige kleine (aber auch nicht so kleine) Fehler entdecken. Ein typisches Beispiel ist, wenn ein Wert vergessen wird zu behandeln
Noneoder eine andere spezielle Bedingung. - Die Refaktorisierung des Codes wird erheblich erleichtert, da das Typprüfsystem oft sehr genau angibt, welchen Code man ändern muss. Dabei müssen wir uns nicht auf eine 100%ige Testabdeckung verlassen, was in jedem Fall normalerweise unerfüllbar ist. Wir müssen nicht die Tiefen von Stack-Trace-Berichten durchforsten, um die Ursache eines Problems herauszufinden.
- Selbst in großen Projekten kann mypy oft innerhalb von Bruchteilen von Sekunden eine vollständige Typüberprüfung durchführen. Das Ausführen von Tests dauert in der Regel jedoch Dutzende von Sekunden oder sogar Minuten. Das Typüberprüfungssystem bietet dem Programmierer sofortiges Feedback und ermöglicht es ihm, seine Arbeit schneller zu erledigen. Er muss nicht länger fragile und schwer wartbare Modultests schreiben, die echte Entitäten durch Mocks und Patches ersetzen, nur um schneller Ergebnisse der Codeprüfung zu erhalten.
IDEs und Editoren wie PyCharm oder Visual Studio Code nutzen die Möglichkeiten von Typannotationen, um Entwicklern Funktionen für die automatische Vervollständigung von Code, zur Hervorhebung von Fehlern und zur Unterstützung häufig verwendeter Sprachkonstrukte bereitzustellen. Und das sind nur einige der Vorteile, die die Typisierung bietet. Für manche Programmierer ist dies das Hauptargument für die Typisierung. Es bringt sofortige Vorteile nach der Einführung in die Arbeit. Diese Art der Nutzung von Typen erfordert nicht den Einsatz eines separaten Typüberprüfungssystems wie mypy, wobei zu beachten ist, dass mypy hilft, die Übereinstimmung von Typannotationen und Code aufrechtzuerhalten.
Vorgeschichte von mypy
Die Geschichte von mypy begann in Großbritannien, in Cambridge, einige Jahre bevor ich zu Dropbox kam. Im Rahmen meiner Doktorarbeit beschäftigte ich mich mit der Vereinheitlichung statisch typisierter und dynamischer Sprachen. Ich wurde von einem Artikel über schrittweise Typisierung von Jeremy Siek und Walid Taha sowie dem Projekt Typed Racket inspiriert. Ich versuchte, Wege zu finden, die gleiche Programmiersprache für verschiedene Projekte zu verwenden - von kleinen Skripten bis hin zu Codebasen mit vielen Millionen Zeilen. Dabei wollte ich vermeiden, dass in Projekten aller Größenordnungen zu große Kompromisse eingegangen werden müssen. Ein wichtiger Teil davon war die Idee eines schrittweisen Übergangs vom untypisierten Prototyp eines Projekts zu einem umfassend getesteten, statisch typisierten fertigen Produkt. Heutzutage werden diese Ideen weitgehend als selbstverständlich angesehen, aber im Jahr 2010 war dies ein Problem, das noch aktiv erforscht wurde.
Meine ursprüngliche Arbeit im Bereich der Typüberprüfung war nicht auf Python ausgerichtet. Stattdessen verwendete ich eine kleine 'hausgemachte' Sprache . Hier ist ein Beispiel, das Ihnen helfen wird zu verstehen, worum es geht (Typannotationen sind hier nicht zwingend erforderlich):
def Fib(n as Int) as Int
if n <= 1
return n
else
return Fib(n - 1) + Fib(n - 2)
end
endDie Verwendung einer vereinfachten Sprache, die selbst entwickelt wurde, ist ein gängiger Ansatz in der wissenschaftlichen Forschung. Dies liegt nicht zuletzt daran, dass es ermöglicht, Experimente schnell durchzuführen und dass alles, was nicht zur Forschung gehört, problemlos ignoriert werden kann. Tatsächlich sind verwendete Programmiersprachen meist umfangreiche Phänomene mit komplexen Implementierungen, was die Experimente verlangsamt. Allerdings wirken alle Ergebnisse, die auf einer vereinfachten Sprache basieren, ein wenig verdächtig, da der Forscher möglicherweise wichtige Überlegungen für die praktische Nutzung der Sprachen opfert.
Mein Typprüfwerkzeug für Alore schien vielversprechend zu sein, aber ich wollte es testen, indem ich Experimente mit echtem Code durchführte, der auf Alore kaum geschrieben wurde. Zu meinem Glück basierte die Sprache Alore weitgehend auf den gleichen Ideen wie Python. Es war ziemlich einfach, das Typprüfwerkzeug so anzupassen, dass es mit der Syntax und Semantik von Python arbeiten konnte. Dies ermöglichte es, die Typprüfung im Open-Source-Python-Code auszuprobieren. Darüber hinaus schrieb ich einen Transpiler, um Code, der in Alore geschrieben wurde, in Python-Code zu transformieren und verwendete ihn für die Übersetzung des Codes meines Typrüfwerkzeugs. Jetzt hatte ich ein Typprüfsystem in Python, das ein Teilmenge von Python unterstützte, eine Art Variationen dieser Sprache! (Bestimmte architektonische Entscheidungen, die für Alore sinnvoll waren, passten schlecht zu Python, was in einigen Teilen der Codebasis von mypy weiterhin deutlich wird.)
Tatsächlich konnte die Sprache, die von meinem Typensystem unterstützt wurde, zu diesem Zeitpunkt nicht wirklich als Python bezeichnet werden: es war eine Variante von Python aufgrund bestimmter Einschränkungen der Python 3-Typannotationssyntax.
Es sah aus wie eine Mischung aus Java und Python:
int fib(int n):
if n <= 1:
return n
else:
return fib(n - 1) + fib(n - 2)Eine meiner Ideen zu der Zeit war es, Typanmerkungen zu verwenden, um die Leistung zu verbessern, indem diese Python-Variante in C oder möglicherweise in JVM-Bytecode kompiliert wird. Ich hatte einen Prototypen eines Compilers geschrieben, gab das Vorhaben jedoch auf, da die Typprüfung an sich bereits als nützlich erschien.
Schließlich stellte ich mein Projekt auf der PyCon 2013 in Santa Clara vor. Zudem sprach ich darüber mit Guido van Rossum, dem großzügigen lebenslangen Diktator von Python. Er überzeugte mich, auf meine eigene Syntax zu verzichten und mich an die standardmäßige Syntax von Python 3 zu halten. Python 3 unterstützt Funktionsanmerkungen, sodass mein Beispiel wie unten gezeigt umgeschrieben werden konnte, um ein normales Python-Programm zu erhalten:
def fib(n: int) -> int:
if n <= 1:
return n
else:
return fib(n - 1) + fib(n - 2)Ich musste einige Kompromisse eingehen (zunächst möchte ich darauf hinweisen, dass ich deshalb meine eigene Syntax erfunden habe). Insbesondere unterstützte Python 3.3, die zu diesem Zeitpunkt aktuellste Version der Sprache, keine Variablenanmerkungen. Ich diskutierte per E-Mail mit Guido verschiedene Möglichkeiten der syntaktischen Gestaltung solcher Anmerkungen. Wir entschieden uns, für Variablen Kommentare mit Typangaben zu verwenden. Dies erreichte zwar das angestrebte Ziel, sah jedoch etwas umständlich aus (Python 3.6 gab uns eine angenehmere Syntax):
products = [] # type: List[str] # EwwTypkommentare waren zudem hilfreich, um Python 2 zu unterstützen, das keine eingebaute Unterstützung für Typanmerkungen hatte:
f fib(n):
# type: (int) -> int
if n <= 1:
return n
else:
return fib(n - 1) + fib(n - 2)Es stellte sich heraus, dass diese (und andere) Kompromisse tatsächlich nicht von großer Bedeutung waren – die Vorteile der statischen Typisierung führten dazu, dass die Benutzer bald die nicht ganz perfekte Syntax vergaßen. Da in nunmehr typüberwachten Python-Codes keine speziellen Syntaxkonstrukte verwendet wurden, funktionierten die bestehenden Python-Tools und -Prozesse zur Codeverarbeitung weiterhin normal, was den Entwicklern das Erlernen des neuen Werkzeugs erheblich erleichterte.
Guido überzeugte mich außerdem, nach meiner Verteidigung der Abschlussarbeit zu Dropbox zu wechseln. Hier beginnt das Interessanteste in der Geschichte von mypy.
Fortsetzung folgt…
Liebe Leser! Wenn Sie Python verwenden, erzählen Sie uns bitte, welche Projekte Sie in dieser Sprache entwickeln.
Quelle: habr.com
