Verwendung strenger Module in großen Python-Projekten: Instagram-Erfahrung. Teil 2

Wir machen Sie auf den zweiten Teil der Übersetzung aufmerksam, der sich mit den Funktionen der Arbeit mit Modulen in Instagram Python-Projekten befasst. Der erste Teil der Übersetzung gab einen Überblick über die Situation und zeigte zwei Probleme auf. Eine davon betrifft den langsamen Start des Servers, die andere die Nebenwirkungen unsicherer Importbefehle. Heute wird dieses Gespräch fortgesetzt. Wir werden ein weiteres Problem betrachten und über Lösungsansätze für alle angesprochenen Probleme sprechen.



Problem 3: Veränderlicher globaler Status


Schauen Sie sich eine andere Kategorie häufiger Fehler an.

def myview(request):     SomeClass.id = request.GET.get("id") 

Hier befinden wir uns in der Präsentationsfunktion und hängen das Attribut an eine bestimmte Klasse an, basierend auf den Daten, die von der Anfrage empfangen wurden. Wahrscheinlich haben Sie das Wesentliche des Problems bereits verstanden. Tatsache ist, dass Klassen globale Singletones sind. Und hier setzen wir den Staat je nach Wunsch in ein langlebiges Objekt. In einem Webserver-Prozess, der lange Zeit in Anspruch nimmt, kann dies dazu führen, dass jede zukünftige Anforderung, die im Rahmen dieses Prozesses gestellt wird, beeinträchtigt wird.

Dasselbe kann bei Tests leicht passieren. Insbesondere in Fällen, in denen Programmierer versuchen, Affen-Patches zu verwenden und nicht den Kontext-Manager wie mock.patch . Dies kann nicht zu einer Verschmutzung der Anforderungen führen, sondern zu einer Verschmutzung aller Tests, die im selben Prozess durchgeführt werden. Dies ist ein schwerwiegender Grund für das unzuverlässige Verhalten unseres Testsystems. Dies ist ein erhebliches Problem, und es ist sehr schwierig, dies zu verhindern. Infolgedessen haben wir das einheitliche Testsystem aufgegeben und auf ein Testisolierungsschema umgestellt, das als „ein Test pro Prozess“ beschrieben werden kann.

Eigentlich ist dies unser drittes Problem. Ein veränderlicher globaler Zustand ist ein Phänomen, das nicht nur in Python vorkommt. Sie können es überall finden. Es handelt sich um Klassen, Module, Listen oder Wörterbücher, die an Module oder Klassen angehängt sind, und um Singleton-Objekte, die auf Modulebene erstellt wurden. Die Arbeit in einem solchen Umfeld erfordert Disziplin. Um eine Verschmutzung des globalen Staates während des Programmablaufs zu verhindern, benötigen Sie sehr gute Python-Kenntnisse.

Einführung in strenge Module


Eine der Hauptursachen für unsere Probleme kann sein, dass wir Python verwenden, um Probleme zu lösen, für die diese Sprache nicht entwickelt wurde. Wenn Sie in kleinen Teams und kleinen Projekten die Regeln bei der Verwendung von Python befolgen, funktioniert diese Sprache einwandfrei. Und wir sollten zu einer strengeren Sprache gehen.

Unsere Codebasis ist jedoch bereits über die Größe hinausgewachsen, die es uns ermöglicht, zumindest darüber zu sprechen, wie wir sie in einer anderen Sprache umschreiben können. Und was noch wichtiger ist, Python hat trotz aller Probleme viel damit zu tun. Er gibt uns mehr gut als schlecht. Unsere Entwickler mögen diese Sprache wirklich. Folglich hängt es nur von uns ab, wie wir Python dazu bringen, auf unserer Skala zu arbeiten, und wie wir sicherstellen, dass wir an dem Projekt weiterarbeiten können, während es sich entwickelt.

Das Finden von Lösungen für unsere Probleme führte uns zu einer Idee. Es besteht aus strengen Modulen.

Strict-Module sind Python-Module eines neuen Typs, an deren Anfang sich eine Konstruktion befindet. __strict__ = True . Sie werden mithilfe vieler der Erweiterungsmechanismen auf niedriger Ebene implementiert, über die Python bereits verfügt. Ein spezielles Modulladeprogramm analysiert den Code mit dem ast Modul, interpretiert den geladenen Code abstrakt, analysiert ihn, wendet verschiedene Transformationen auf den AST an und kompiliert den AST mit der integrierten compile wieder in Python-Bytecode.

Keine Importnebenwirkungen


Strenge Module beschränken das, was auf Modulebene passieren kann. Daher muss jeder Code auf Modulebene (einschließlich der auf Modulebene aufgerufenen Dekoratoren und Funktionen / Initialisierer) sauber sein, d. H. Code, der frei von Nebenwirkungen ist und keine E / A-Mechanismen verwendet. Diese Bedingungen werden vom Abstract-Interpreter mithilfe der statischen Codeanalyse zur Kompilierungszeit überprüft.

Dies bedeutet, dass die Verwendung strenger Module beim Importieren keine Nebenwirkungen verursacht. Während des Modulimports ausgeführter Code kann keine unerwarteten Probleme mehr verursachen. Da wir dies auf der Ebene der abstrakten Interpretation überprüfen und Tools verwenden, die eine große Teilmenge von Python verstehen, müssen wir die Ausdruckskraft von Python nicht übermäßig einschränken. Viele Arten von dynamischem Code ohne Nebenwirkungen können sicher auf Modulebene verwendet werden. Dies beinhaltet verschiedene Dekoratoren und die Definition von Konstanten auf Modulebene mithilfe von Listen oder Wörterbuchgeneratoren.

Machen wir es klarer, betrachten wir ein Beispiel. Hier ist das korrekt geschriebene strikte Modul:

 """Module docstring.""" __strict__ = True from utils import log_to_network MY_LIST = [1, 2, 3] MY_DICT = {x: x+1 for x in MY_LIST} def log_calls(func):    def _wrapped(*args, **kwargs):        log_to_network(f"{func.__name__} called!")        return func(*args, **kwargs)    return _wrapped @log_calls def hello_world():    log_to_network("Hello World!") 

In diesem Modul können wir die üblichen Python-Konstrukte verwenden, einschließlich des dynamischen Codes, der zum Erstellen des Wörterbuchs verwendet wird, und des Dekors auf Modulebene. Gleichzeitig ist der Zugriff auf Netzwerkressourcen in den Funktionen _wrapped oder hello_world völlig normal. Tatsache ist, dass sie nicht auf Modulebene aufgerufen werden.

Wenn wir jedoch den Aufruf log_to_network in die externe Funktion log_calls oder versuchen, einen Decorator zu verwenden, der Nebenwirkungen verursacht (wie @route aus dem vorherigen Beispiel), oder wenn wir den Aufruf hello_world() auf Modulebene verwenden, ist er nicht mehr streng -Modul.

Wie kann man herausfinden, dass es nicht sicher ist, log_to_network oder route Funktionen auf Modulebene log_to_network ? Wir gehen von der Annahme aus, dass alles, was aus Modulen importiert wird, die keine strengen Module sind, unsicher ist, mit Ausnahme einiger Funktionen aus der Standardbibliothek, die als sicher bekannt sind. Wenn das utils Modul ein striktes Modul ist, können wir uns auf die Analyse unseres Moduls verlassen, um uns log_to_network , ob die log_to_network Funktion log_to_network .

Importe, die keine Nebenwirkungen haben, verbessern nicht nur die Code-Zuverlässigkeit, sondern beseitigen auch ein ernstes Hindernis für das Sichern inkrementeller Code-Downloads. Dies eröffnet weitere Möglichkeiten, um Möglichkeiten zur Beschleunigung von Importteams auszuloten. Wenn der Code auf Modulebene frei von Nebenwirkungen ist, können wir auf Anfrage beim Zugriff auf die Modulattribute einzelne Modulanweisungen sicher im "Lazy" -Modus ausführen. Dies ist viel besser, als dem "gierigen" Algorithmus zu folgen, in dessen Anwendung der gesamte Modulcode im Voraus ausgeführt wird. Unter Berücksichtigung der Tatsache, dass die Form aller Klassen im Modul strict zum Zeitpunkt der Kompilierung vollständig bekannt ist, können wir in Zukunft sogar versuchen, die dauerhafte Speicherung der während der Codeausführung generierten Modulmetadaten (Klassen, Funktionen, Konstanten) zu organisieren. Auf diese Weise können wir den schnellen Import von unveränderten Modulen organisieren, ohne dass der Bytecode der Modulebene wiederholt ausgeführt werden muss.

Immunität und __slots__ Attribut


Strikte Module und Klassen, die in ihnen deklariert sind, sind nach ihrer Erstellung unveränderlich. Module werden mit Hilfe der internen Transformation des Modulkörpers in eine Funktion unveränderlich gemacht, bei der der Zugriff auf alle globalen Variablen über Closure-Variablen organisiert wird. Diese Änderungen haben die Möglichkeiten für eine zufällige Änderung des globalen Zustands erheblich verringert, obwohl der veränderbare globale Zustand immer noch berechnet werden kann, wenn entschieden wird, ihn durch veränderbare Container auf Modulebene zu verwenden.

Mitglieder von Klassen, die in strengen Modulen deklariert sind, müssen auch in __init__ deklariert __init__ . Sie werden automatisch in das Attribut __slots__ geschrieben, während die AST-Umwandlung vom __slots__ wird. Infolgedessen können Sie später der Klasseninstanz keine weiteren Attribute mehr hinzufügen. Hier ist eine ähnliche Klasse:

 class Person:    def __init__(self, name, age):        self.name = name        self.age = age 

Während der AST-Transformation, die während der Verarbeitung strenger Module ausgeführt wird, werden die in __init__ Vorgänge zum Zuweisen von Werten zu den name und __init__ erkannt und ein Attribut der Form __slots__ = ('name', 'age') an die Klasse angehängt. Dadurch wird verhindert, dass der Klasseninstanz weitere Attribute hinzugefügt werden. (Wenn Typanmerkungen verwendet werden, berücksichtigen wir Informationen zu den auf Klassenebene verfügbaren Typen, z. B. name: str , und fügen sie der Liste der Slots hinzu.)

Die beschriebenen Einschränkungen machen den Code nicht nur zuverlässiger. Sie beschleunigen die Codeausführung. Die automatische Umwandlung von Klassen durch Hinzufügen des Attributs __slots__ erhöht die Effizienz der Speichernutzung beim Arbeiten mit diesen Klassen. Auf diese Weise können Sie bei der Arbeit mit einzelnen Instanzen von Klassen die Dictionary-Suche umgehen und den Zugriff auf Attribute beschleunigen. Darüber hinaus können wir diese Muster während der Ausführung von Python-Code weiter optimieren, wodurch wir unser System weiter verbessern können.

Zusammenfassung


Strikte Module sind immer noch experimentelle Technologie. Wir haben einen funktionierenden Prototyp und befinden uns in einem frühen Stadium der Bereitstellung dieser Funktionen in der Produktion. Wir hoffen, dass wir, nachdem wir genug Erfahrung im Umgang mit Strict-Modulen gesammelt haben, mehr über sie sprechen können.

Sehr geehrte Leser! Denken Sie, dass die Funktionen von strict-Modulen in Ihrem Python-Projekt nützlich sind?


Source: https://habr.com/ru/post/de475242/


All Articles