Hinweis: Obwohl JavaScript für diese Website nicht unbedingt erforderlich ist, werden Ihre Interaktionsmöglichkeiten mit den Inhalten eingeschränkt sein. Bitte aktivieren Sie JavaScript für das volle Erlebnis.

Robuste Codebasen mit Pythons Typ-Annotationen aufbauen

Die Python-Codebasis von Hudson River Trading (HRT) ist groß und entwickelt sich ständig weiter. Millionen von Zeilen Python spiegeln die Arbeit von Hunderten von Entwicklern im Laufe des letzten Jahrzehnts wider. Wir handeln auf über 200 Märkten weltweit – einschließlich fast aller elektronischen Märkte der Welt –, daher müssen wir unseren Code regelmäßig aktualisieren, um Änderungen an Regeln und Vorschriften zu berücksichtigen.

Unsere Codebasis bietet Befehlszeilenschnittstellen (CLI), grafische Benutzeroberflächen (GUIs) und ereignisgesteuerte Prozesse, die unsere Händler, Ingenieure und Betriebspersonal unterstützen. Diese äußere Schicht unserer Codebasis wird von einer inneren Schicht gemeinsam genutzter Geschäftslogik unterstützt. Geschäftslogik ist oft komplexer, als sie erscheint: Selbst eine einfache Frage wie „Was ist der nächste Geschäftstag für NASDAQ?“ beinhaltet die Abfrage einer Datenbank mit Marktkalendern (eine Datenbank, die regelmäßige Wartung erfordert). Durch die Zentralisierung dieser Geschäftslogik in einer einzigen Quelle der Wahrheit stellen wir sicher, dass alle verschiedenen Systeme in unserer Codebasis kohärent funktionieren.

Selbst eine kleine Änderung an gemeinsam genutzter Geschäftslogik kann viele Systeme beeinflussen, und wir müssen überprüfen, ob diese Systeme keine Probleme mit unserer Änderung haben. Es ist ineffizient und fehleranfällig, wenn ein Mensch manuell überprüft, ob nichts kaputt ist. Pythons Typ-Annotationen haben maßgeblich dazu beigetragen, wie schnell wir Änderungen an gemeinsam genutzter Geschäftslogik aktualisieren und überprüfen können.

Typ-Annotationen ermöglichen es Ihnen, den Datentyp zu beschreiben, mit dem Ihr Code arbeitet. „Typ-Checker“ sind Werkzeuge, die Ihre Beschreibungen mit der tatsächlichen Verwendung des Codes abgleichen. Wenn wir gemeinsam genutzte Geschäftslogik aktualisieren, aktualisieren wir die Typ-Annotationen und verwenden einen Typ-Checker, um alle nachgelagerten Systeme zu identifizieren, die betroffen sind.

Wir dokumentieren und testen unsere Codebasis auch gründlich. Aber schriftliche Dokumentation ist nicht automatisch mit dem zugrunde liegenden Code synchronisiert, daher erfordert die Pflege der Dokumentation ein hohes Maß an Wachsamkeit und ist menschlichen Fehlern unterworfen. Darüber hinaus sind automatisierte Tests auf die Szenarien beschränkt, für die wir testen, was bedeutet, dass neuartige Verwendungen unserer gemeinsam genutzten Geschäftslogik bis zur Hinzufügung neuer Tests unüberprüft bleiben.


Betrachten wir ein Beispiel für Typ-Annotationen, um zu sehen, wie sie zur Beschreibung der Datenform verwendet werden können. Hier ist Python-Code mit Typ-Annotationen, der die Prüfziffer eines CUSIP berechnet.

def cusip_checksum(cusip8: str) -> int:
    assert len(cusip8) == 8
    chars: str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*@#"
    charmap: dict[str, int] = {
        char: value
        for value, char in enumerate(chars, start=0)
    }
    total: int  = 0
    for idx, char in enumerate(cusip8, start=0):
        value: int = charmap[char]
        if (idx % 2) == 1:
            value *= 2
        total += (value // 10) + (value % 10)
    return (10 - total % 10) % 10

Hier ist, was die Typ-Annotationen uns sagen

  • cusip_checksum() ist eine Funktion, die eine Zeichenkette als Eingabe nimmt und eine Ganzzahl als Ausgabe zurückgibt.
  • chars ist eine Zeichenkette.
  • charmap ist ein Wörterbuch mit Zeichenketten als Schlüsseln und Ganzzahlen als Werten.
  • total und value sind Ganzzahlen.

HRT verwendet mypy, um unsere Python-Typ-Annotationen zu analysieren. Mypy analysiert die Typ-Annotationen in einer oder mehreren Python-Dateien und ermittelt, ob es Probleme oder Inkonsistenzen gibt.

Mypy examples

Meistens ist mypy gut in der Typinferenz, daher ist es besser, sich auf die Annotation von Parametern und Rückgabewerten einer Funktion zu konzentrieren, anstatt auf die internen Variablen, die in einer Funktion verwendet werden.


Hier ist eine neue Funktion, validate_cusip(), die sich auf die Funktion cusip_checksum() von zuvor stützt.

def cusip_checksum(cusip8: str) -> int:
    ...

def validate_cusip(cusip: str) -> str | None:
    checksum: int
    if len(cusip) == 9:
        checksum = cusip_checksum(cusip[:8])
        if str(checksum) == cusip[8]:
            return cusip
        else:
            return None
    elif len(cusip) == 8:
        checksum = cusip_checksum(cusip)
        return f"{cusip}{checksum}"
    else:
        return None

Mypy ist mit diesem Code zufrieden

Success: no issues found in 1 source file

Nehmen wir nun an, wir entscheiden uns, cusip_checksum() so zu aktualisieren, dass es None zurückgibt, wenn es feststellt, dass der CUSIP ungültig ist.

def cusip_checksum(cusip8: str) -> int | None:
    if len(cusip8) != 8:
        return None
    chars: str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ*@#"
    charmap: dict[str, int] = {
        char: value
        for value, char in enumerate(chars, start=0)
    }
    total: int  = 0
    for idx, char in enumerate(cusip8, start=0):
        try:
            value: int = charmap[char]
        except KeyError:
            return None
        if (idx % 2) == 1:
            value *= 2
        total += (value // 10) + (value % 10)
    return (10 - total % 10) % 10

Mypy erkennt automatisch Probleme bei der Verwendung von cusip_checksum() durch validate_cusip().

error: Incompatible types in assignment (expression has type "int | None", variable has type "int")  [assignment]

Nachdem wir nun benachrichtigt wurden, können wir validate_cusip() aktualisieren, um diese Änderungen zu berücksichtigen.

def cusip_checksum(cusip8: str) -> int | None:
    ...

def validate_cusip(cusip: str) -> str | None:
    if len(cusip) == 9:
        match cusip_checksum(cusip[:8]):
            case int(checksum) if str(checksum) == cusip[8]:
                return cusip
    elif len(cusip) == 8:
        match cusip_checksum(cusip):
            case int(checksum):
                return f"{cusip}{checksum}"
    return None

In diesem Beispiel befanden sich die Funktionen im Quellcode nebeneinander. Aber Mypy glänzt wirklich, wenn die Funktionen über viele Dateien in der Codebasis verteilt sind.


Alles in allem bieten Typ-Annotationen erhebliche Vorteile, um Ihre Codebasis robuster zu machen. Sie sind keine Alles-oder-Nichts-Angelegenheit – Sie können sich darauf konzentrieren, Typ-Annotationen zu kleinen Teilen Ihrer Codebasis hinzuzufügen und die Menge des mit Typen versehenen Codes im Laufe der Zeit zu erweitern. Zusammen mit anderen Technologien helfen Pythons Typ-Annotationen HRT, in der schnelllebigen Welt des globalen Handels weiterhin erfolgreich zu sein.

Dieser Artikel erschien ursprünglich im HRT Beat.

Lernen Sie den Autor kennen

John Lekberg arbeitet bei HRT an einer Reihe von Python- und gRPC-Systemen. Er entwickelt und verfeinert hauptsächlich interne Werkzeuge für Überwachung und Alarmierung. Er hat auch Initiativen geleitet, statische Analysetools auf die HRT-Codebasis anzuwenden, Fehler zu erkennen und den manuellen Aufwand für die Codeüberprüfung zu reduzieren.