Hallo allerseits! Heute erzähle ich Ihnen die Geschichte der Entwicklung des Schreibens am Beispiel eines der Projekte in
Ostrovok.ru .

Diese Geschichte begann lange vor dem
Tipp-Hype in
Python3.5 und begann in einem in
Python2.7 geschriebenen
Projekt .
2013 : Erst kürzlich gab es eine Version von
python3.3 . Es machte keinen Sinn, auf die neue Version zu migrieren, da keine spezifischen Funktionen hinzugefügt wurden und es während des Übergangs viel Schmerz und Leid geben würde.
Ich war am Partnerprojekt bei Ostrovok.ru beteiligt - dieser Service war für alles verantwortlich, was mit Partnerintegrationen, Reservierungen, Statistiken und einem persönlichen Konto zu tun hat. Wir haben sowohl interne APIs für andere Microservices des Unternehmens als auch eine externe API für unsere Partner verwendet.
Irgendwann formulierte das Team den folgenden Ansatz zum Schreiben von HTTP-Handlern oder einer Art Geschäftslogik:
1) Die Eingabe- und Ausgabedaten müssen durch eine Struktur (Klasse) beschrieben werden.
2) Der Inhalt von Instanzen von Strukturen muss gemäß der Beschreibung validiert werden.
3) Eine Funktion, die eine Struktur am Eingang annimmt und die Struktur am Ausgang angibt, sollte die Datentypen am Eingang bzw. am Ausgang überprüfen.
Ich werde nicht auf jeden Punkt im Detail eingehen. Das folgende Beispiel sollte ausreichen, um zu verstehen, worum es geht.
Beispiel.
import datetime as dt from contracts import new_contract, contract from schematics.models import Model from schematics.types import IntType, DateType
Das Beispiel verwendet Bibliotheken:
Schaltpläne und
Pycontracts .
*
Schaltpläne - eine Möglichkeit, Daten zu beschreiben und zu validieren.
*
pycontracts - eine Möglichkeit, die Eingabe / Ausgabe einer Funktion zur Laufzeit zu überprüfen.
Mit diesem Ansatz können Sie:
- Es ist einfacher, Tests zu schreiben - Probleme mit der Validierung treten nicht auf, und nur die Geschäftslogik wird behandelt.
- Um das Format und die Qualität der Antwort in der API zu gewährleisten, erscheint ein starrer Rahmen für das, was wir zu akzeptieren bereit sind und was wir geben können.
- Es ist einfacher, das Antwortformat zu verstehen / umzugestalten, wenn es sich um eine komplexe Struktur mit unterschiedlichen Verschachtelungsebenen handelt.
Es ist wichtig zu verstehen, dass die Typprüfung (Nichtvalidierung) nur zur
Laufzeit funktioniert. Dies ist praktisch für die lokale Entwicklung, das Ausführen von Tests in CI und das Überprüfen, ob die Kandidatenversion in einer
Staging- Umgebung funktioniert. In einer Produktionsumgebung muss dies deaktiviert werden, da sonst der Server langsamer wird.
Jahre vergingen, unser Projekt wuchs, es erschien eine neue und komplexere Geschäftslogik, die Anzahl der API-Handles nahm zumindest nicht ab.
Irgendwann bemerkte ich, dass der Start des Projekts bereits einige Sekunden dauerte - das war ärgerlich, weil ich jedes Mal, wenn ich den Code bearbeitete und die Tests durchführte, lange warten musste. Als dieses Warten 8-10 Sekunden dauerte, beschlossen wir schließlich herauszufinden, was unter der Haube vor sich ging.
Tatsächlich stellte sich heraus, dass alles recht einfach war. Beim Starten eines Projekts analysiert die pycontracts-Bibliothek alle von
@contract abgedeckten
Dokumentzeichenfolgen, um alle Strukturen im Speicher zu registrieren und sie dann korrekt zu überprüfen. Wenn die Anzahl der Strukturen in einem Projekt Tausende beträgt, verlangsamt sich das Ganze.
Was tun? Die richtige Antwort ist, nach anderen Lösungen zu suchen, zum Glück ist in der Werft bereits
2018 (
python3.5 -
python3.6 ), und wir haben unser Projekt bereits auf
python3.6 migriert .
Ich begann alternative Lösungen zu studieren und
überlegte, wie ich ein Projekt von "
Pycontracts + Typbeschreibung in
Docstring " zu "
Something + Type Description in
Typing Annotation "
migrieren kann . Es stellte sich heraus, dass Sie beim Upgrade von
pycontracts auf die neueste Version Typen im Typ "
Annotation Style" beschreiben können. Dies könnte beispielsweise folgendermaßen aussehen:
@contract def get_order_info(data_in: OrderInfoData) -> OrderInfoResult: return OrderInfoResult( dict( order_id=data_in.order_id, checkin_at=dt.datetime.today(), checkout_at=dt.datetime.today() + dt.timedelta(days=1), cancelled_at=None, ) )
Probleme beginnen, wenn Sie Strukturen aus der
Eingabe verwenden müssen , z. B.
Optional oder
Union , da
pycontracts NICHT weiß, wie man mit ihnen arbeitet:
from typing import Optional @contract def get_order_info(data_in: OrderInfoData) -> Optional[OrderInfoResult]: return OrderInfoResult( dict( order_id=data_in.order_id, checkin_at=dt.datetime.today(), checkout_at=dt.datetime.today() + dt.timedelta(days=1), cancelled_at=None, ) )
Ich suchte nach alternativen Bibliotheken für die Typprüfung in der
Laufzeit :
*
durchsetzen*
Typeguard*
PytypenEnforce unterstützte zu diesem Zeitpunkt
python3.7 nicht , aber wir haben bereits aktualisiert,
pytypes mochte die Syntax nicht, daher fiel die Wahl auf
typeguard .
from typeguard import typechecked @typechecked def get_order_info(data_in: OrderInfoData) -> Optional[OrderInfoResult]: return OrderInfoResult( dict( order_id=data_in.order_id, checkin_at=dt.datetime.today(), checkout_at=dt.datetime.today() + dt.timedelta(days=1), cancelled_at=None, ) )
Hier einige Beispiele aus einem realen Projekt:
@typechecked def view( request: HttpRequest, data_in: AffDeeplinkSerpIn, profile: Profile, contract: Contract, ) -> AffDeeplinkSerpOut: ... @typechecked def create_contract( user: Union[User, AnonymousUser], user_uid: Optional[str], params: RegistrationCreateSchemaIn, account_manager: Manager, support_manager: Manager, sales_manager: Optional[Manager], legal_entity: LegalEntity, partner: Partner, ) -> tuple: ... @typechecked def get_metaorder_ids_from_ordergroup_orders( orders: Tuple[OrderGroupOrdersIn, ...], contract: Contract ) -> list: ...
Infolgedessen konnten wir nach einem langen Refactoring-Prozess das Projekt vollständig auf
typeguard +
typing annotations übertragen .
Welche Ergebnisse haben wir erzielt:
- Das Projekt startet in 2-3 Sekunden, was zumindest nicht nervt.
- Die Lesbarkeit des Codes hat sich verbessert.
- Das Projekt ist sowohl in der Anzahl der Zeilen als auch in den Dateien kleiner geworden, da keine Strukturregistrierungen mehr über @new_contract erfolgen .
- Smart PyCharm IDEs sind besser darin geworden, ein Projekt zu indizieren und andere Hinweise zu geben, da es sich jetzt nicht mehr um Kommentare, sondern um ehrliche Importe handelt.
- Sie können statische Analysegeräte wie mypy und pyre-check verwenden , da sie das Arbeiten mit Tippanmerkungen unterstützen.
- Die gesamte Python-Community tendiert dazu, in der einen oder anderen Form zu tippen, dh aktuelle Aktionen sind Investitionen in die Zukunft des Projekts.
- Manchmal gibt es Probleme mit zyklischen Importen, aber es gibt nur wenige, und sie können vernachlässigt werden.
Ich hoffe, dieser Artikel wird Ihnen nützlich sein!
Referenzen:
*
durchsetzen*
Typeguard*
Pytypen*
Pycontracts*
Schaltpläne