So kam es, dass ich seit 2012 als einziger Programmierer einen Open-Source-Browser entwickle. In Python für sich. Der Browser ist nicht die einfachste Sache, jetzt gibt es im Hauptteil des Projekts mehr als 1000 Module und mehr als 120.000 Zeilen Python-Code. Insgesamt wird es bei Satellitenprojekten eineinhalb Mal so viel sein.
Irgendwann hatte ich es satt, am Anfang jeder Datei mit den Importböden herumzuspielen, und entschied mich, dieses Problem ein für alle Mal zu lösen. So wurde die
smart_imports- Bibliothek geboren (
github ,
pypi ).
Die Idee ist ganz einfach. Jedes komplexe Projekt bildet schließlich eine eigene Vereinbarung über die Benennung von allem.
Wenn diese Vereinbarung in formellere Regeln umgewandelt wird, kann jede Entität automatisch unter dem Namen der ihr zugeordneten Variablen importiert werden.Zum Beispiel müssen Sie keine
import math
schreiben,
import math
auf
math.pi
- wir können
math.pi
verstehen, dass
math
in diesem Fall ein Modul der Standardbibliothek ist.
Intelligente Importe unterstützen Python> = 3.5. Die Bibliothek wird vollständig durch Tests abgedeckt,
Abdeckung> 95% . Ich benutze es jetzt seit einem Jahr selbst.
Für Details lade ich Sie zu Cat ein.
Wie funktioniert es im Allgemeinen?
Der Code aus dem Header-Bild funktioniert also wie folgt:
- Während eines Aufrufs von
smart_imports.all()
Bibliothek den AST des Moduls, von dem aus der Aufruf erfolgt. - Finden Sie nicht initialisierte Variablen;
- Wir führen den Namen jeder Variablen durch eine Folge von Regeln, die versuchen, das Modul (oder Modulattribut) zu finden, das für den Import nach Namen benötigt wird. Wenn eine Regel die erforderliche Entität gefunden hat, werden die folgenden Regeln nicht überprüft.
- Die gefundenen Module werden geladen, initialisiert und im globalen Namespace abgelegt (oder die erforderlichen Attribute dieser Module werden dort abgelegt).
Nicht initialisierte Variablen werden im gesamten Code durchsucht, einschließlich der neuen Syntax.
Der automatische Import ist nur für Projektkomponenten aktiviert, die explizit
smart_imoprts.all()
aufrufen. Darüber hinaus verbietet die Verwendung intelligenter Importe nicht die Verwendung herkömmlicher Importe. Auf diese Weise können Sie die Bibliothek schrittweise implementieren und komplexe zyklische Abhängigkeiten auflösen.
Ein akribischer Leser wird feststellen, dass das AST-Modul zweimal aufgebaut ist:
- CPython erstellt es zum ersten Mal während des Modulimports.
- Das zweite Mal, wenn smart_imports es während eines Aufrufs von
smart_imports.all()
.
AST kann wirklich nur einmal erstellt werden (dazu müssen Sie mithilfe von in
PEP-0302 implementierten Import-Hooks in den Importprozess von Modulen integrieren, aber diese Lösung verlangsamt den Import.
Warum denkst du so?Beim Vergleich der Leistung von zwei Implementierungen (mit und ohne Hooks) kam ich zu dem Schluss, dass CPython beim Importieren eines Moduls AST in seinen internen (C-shh) Datenstrukturen erstellt. Das Konvertieren in Python-Datenstrukturen ist teurer als das
Erstellen eines Baums aus der Quelle mit dem
ast- Modul.
Natürlich wird der AST jedes Moduls nur einmal pro Start erstellt und analysiert.
Standardimportregeln
Die Bibliothek kann ohne zusätzliche Konfiguration verwendet werden. Standardmäßig werden Module nach folgenden Regeln importiert:
- Durch genaues Zusammentreffen des Namens wird nach dem Modul neben dem aktuellen (im selben Verzeichnis) gesucht.
- Überprüft die Module der Standardbibliothek:
- durch genaue Übereinstimmung des Namens für Pakete der obersten Ebene;
- Bei verschachtelten Paketen und Modulen wird nach zusammengesetzten Namen gesucht und Punkte durch Unterstriche ersetzt. Beispielsweise wird
os.path
importiert, wenn die Variable os_path
.
- Durch die genaue Übereinstimmung des Namens wird nach installierten Paketen von Drittanbietern gesucht. Zum Beispiel die bekannten Paketanfragen .
Leistung
Intelligente Importe wirken sich nicht auf die Leistung des Programms aus, erhöhen jedoch die Startzeit.
Aufgrund des Umbaus des AST erhöht sich die Zeit des ersten Laufs um das 1,5- bis 2-fache. Für kleine Projekte ist dies nicht von Bedeutung. In großen Projekten leidet die Startzeit eher unter der Abhängigkeitsstruktur zwischen den Modulen als unter der Importzeit eines bestimmten Moduls.
Wenn intelligente Importe populär werden, schreibe ich die Arbeit von AST auf C um - dies sollte die Startkosten erheblich senken.
Um das Laden zu beschleunigen, können die Ergebnisse der Verarbeitung von AST-Modulen im Dateisystem zwischengespeichert werden. Das Caching ist in der Konfiguration aktiviert. Natürlich ist der Cache deaktiviert, wenn Sie die Quelle ändern.
Die Startzeit wird sowohl von der Liste der Modul-Suchregeln als auch von deren Reihenfolge beeinflusst. Da einige Regeln die Standard-Python-Funktionalität verwenden, um nach Modulen zu suchen. Sie können diese Kosten ausschließen, indem Sie die Übereinstimmung von Namen und Modulen mithilfe der Regel "Benutzerdefinierte Namen" explizit angeben (siehe unten).
Konfiguration
Die Standardkonfiguration wurde bereits beschrieben. Es sollte ausreichen, in kleinen Projekten mit der Standardbibliothek zu arbeiten.
Standardkonfiguration { "cache_dir": null, "rules": [{"type": "rule_local_modules"}, {"type": "rule_stdlib"}, {"type": "rule_predefined_names"}, {"type": "rule_global_modules"}] }
Bei Bedarf kann eine komplexere Konfiguration in das Dateisystem eingefügt werden.
Ein Beispiel für eine komplexe Konfiguration (über einen Browser).
Während eines Aufrufs von
smart_import.all()
ermittelt
smart_import.all()
Bibliothek die Position des aufrufenden Moduls im Dateisystem und sucht nach der Datei
smart_imports.json
in der Richtung vom aktuellen Verzeichnis zum Stammverzeichnis. Wenn eine solche Datei gefunden wird, wird sie als Konfiguration für das aktuelle Modul betrachtet.
Sie können mehrere verschiedene Konfigurationen verwenden (in verschiedenen Verzeichnissen ablegen).
Derzeit gibt es nicht viele Konfigurationsoptionen:
{ // AST. // null — . "cache_dir": null|"string", // . "rules": [] }
Regeln importieren
Die Reihenfolge der Angabe von Regeln in der Konfiguration bestimmt die Reihenfolge ihrer Anwendung. Die erste Regel, die funktioniert hat, stoppt die weitere Suche nach Importen.
In den Beispielen für Konfigurationen wird die Regel rule_predefined_names häufig
rule_predefined_names
angezeigt. Es ist erforderlich, dass die integrierten Funktionen (z. B.
print
) korrekt erkannt werden.
Regel 1: Vordefinierte Namen
Mit dieser Regel können Sie vordefinierte Namen wie
__file__
und integrierte Funktionen wie
print
ignorieren.
Regel 2: Lokale Module
Überprüft, ob sich neben dem aktuellen Modul (im selben Verzeichnis) ein Modul mit dem angegebenen Namen befindet. Wenn ja, importiert es.
Regel 3: Globale Module
Versucht, ein Modul direkt nach Namen zu importieren. Zum Beispiel das
Anforderungsmodul .
Regel 4: Benutzerdefinierte Namen
Entspricht dem Namen eines bestimmten Moduls oder seines Attributs. Die Konformität wird in der Regelkonfiguration angegeben.
Regel 5: Standardmodule
Überprüft, ob der Name ein Standardbibliotheksmodul ist. Zum Beispiel
math oder
os.path, das sich in
os_path
verwandelt.
Es funktioniert schneller als die Regel zum Importieren globaler Module, da überprüft wird, ob ein Modul in einer zwischengespeicherten Liste vorhanden ist. Listen für jede Python-Version finden Sie hier:
github.com/jackmaney/python-stdlib-listRegel 6: Import nach Präfix
Importiert ein Modul nach Namen aus dem Paket, das seinem Präfix zugeordnet ist. Dies ist praktisch, wenn im gesamten Code mehrere Pakete verwendet werden. Beispielsweise kann auf die
utils
utils_
mit dem Präfix
utils_
.
Regel 7: Das Modul aus dem übergeordneten Paket
Wenn Sie in verschiedenen Teilen des Projekts Unterpakete mit demselben Namen haben (z. B.
tests
oder
migrations
), können Sie ihnen erlauben, nach Modulen zu suchen, die in den übergeordneten Paketen nach Namen importiert werden sollen.
Regel 8: Bindung an ein anderes Paket
Für Module aus einem bestimmten Paket ermöglicht es die Suche nach Importen nach Namen in anderen Paketen (in der Konfiguration angegeben). In meinem Fall war diese Regel nützlich für Fälle, in denen ich die Arbeit der vorherigen Regel (Modul aus dem übergeordneten Paket) nicht auf das gesamte Projekt ausweiten wollte.
Hinzufügen eigener Regeln
Das Hinzufügen einer eigenen Regel ist ziemlich einfach:
- Wir erben von der Klasse smart_imports.rules.BaseRule .
- Wir erkennen die notwendige Logik.
- Registrieren Sie eine Regel mit der Methode smart_imports.rules.register
- Fügen Sie die Regel zur Konfiguration hinzu.
- ???
- Gewinn
Ein Beispiel findet sich in der
Umsetzung der aktuellen Regeln.Gewinn
Mehrzeilige Importlisten am Anfang jeder Quelle sind verschwunden.
Die Anzahl der Zeilen hat abgenommen. Bevor der Browser auf intelligente Importe umstellte, waren 6688 Zeilen für den Import verantwortlich. Nach dem Übergang blieb 2084 übrig (zwei Zeilen smart_imports pro Datei + 130 Importe, die explizit von Funktionen und ähnlichen Stellen aufgerufen wurden).
Ein schöner Bonus war die Standardisierung der Namen im Projekt. Code ist leichter zu lesen und leichter zu schreiben. Sie müssen nicht über die Namen der importierten Entitäten nachdenken - es gibt einige klare Regeln, die leicht zu befolgen sind.
Entwicklungspläne
Ich mag die Idee, Codeeigenschaften durch Variablennamen zu definieren, daher werde ich versuchen, sie sowohl in intelligenten Importen als auch in anderen Projekten zu entwickeln.
In Bezug auf intelligente Importe plane ich:
- Unterstützung für neue Versionen von Python hinzufügen.
- Erkunden Sie die Möglichkeit, sich bei der Typanmerkung von Code auf die aktuellen Community-Praktiken zu verlassen.
- Entdecken Sie die Möglichkeit, faul zu importieren.
- Implementieren Sie Dienstprogramme zur automatischen Generierung einer Konfiguration aus Quellcodes und zum Refactoring von Quellen für die Verwendung von smart_imports.
- Schreiben Sie einen Teil des C-Codes neu, um die Arbeit mit dem AST zu beschleunigen.
- Entwicklung der Integration mit Lintern und IDEs, wenn diese Probleme mit der Codeanalyse ohne explizite Importe haben.
Darüber hinaus interessiert mich Ihre Meinung zum Standardverhalten der Bibliothek und zu den Importregeln.
Vielen Dank, dass Sie dieses Textblatt überwältigt haben :-D