Ich habe ein neues Muster entdeckt. Ist dieses Muster bekannt oder wie ist die Meinung dazu?
Grundsätzlich fällt es mir schwer, Quelldateien nach oben und unten zu scrubben, um herauszufinden, welche Modulimporte verfügbar sind, und so weiter
import foo
from bar.baz import quux
def myFunction():
foo.this.that(quux)
Ich verschiebe alle meine Importe in die Funktion, in der sie tatsächlich verwendet werden.
def myFunction():
import foo
from bar.baz import quux
foo.this.that(quux)
Dies macht ein paar Dinge. Erstens verschmutzte ich meine Module selten versehentlich mit dem Inhalt anderer Module. Ich könnte die __all__
Variable für das Modul festlegen , aber dann müsste ich sie aktualisieren, wenn sich das Modul weiterentwickelt, und das hilft nicht bei der Verschmutzung des Namespaces für Code, der tatsächlich im Modul lebt.
Zweitens habe ich selten eine Litanei von Importen am oberen Rand meiner Module, von denen ich die Hälfte oder mehr nicht mehr benötige, weil ich sie überarbeitet habe. Schließlich finde ich dieses Muster VIEL einfacher zu lesen, da jeder referenzierte Name genau dort im Funktionskörper ist.
quelle
Antworten:
Die (zuvor) am besten gewählte Antwort auf diese Frage ist gut formatiert, aber in Bezug auf die Leistung absolut falsch. Lassen Sie mich demonstrieren
Performance
Top Import
import random def f(): L = [] for i in xrange(1000): L.append(random.random()) for i in xrange(1000): f() $ time python import.py real 0m0.721s user 0m0.412s sys 0m0.020s
In Funktionskörper importieren
def f(): import random L = [] for i in xrange(1000): L.append(random.random()) for i in xrange(1000): f() $ time python import2.py real 0m0.661s user 0m0.404s sys 0m0.008s
Wie Sie sehen können, kann es sein , mehr effizient das Modul in der Funktion zu importieren. Der Grund dafür ist einfach. Es verschiebt die Referenz von einer globalen Referenz zu einer lokalen Referenz. Dies bedeutet, dass der Compiler zumindest für CPython
LOAD_FAST
Anweisungen anstelle vonLOAD_GLOBAL
Anweisungen ausgibt. Diese sind, wie der Name schon sagt, schneller. Der andere Antwortende hat den Leistungseinbruch beim Einschauen künstlich erhöht,sys.modules
indem er bei jeder einzelnen Iteration der Schleife importiert hat .In der Regel ist es am besten, oben zu importieren, aber die Leistung ist nicht der Grund, wenn Sie häufig auf das Modul zugreifen. Die Gründe dafür sind, dass man leichter verfolgen kann, wovon ein Modul abhängt, und dass dies mit dem größten Teil des restlichen Python-Universums übereinstimmt.
quelle
LOAD_FAST
aus Leistungsgründenimport random
auf globaler Ebene möchten und dann denrandom_ = random
lokalen Bereich festlegen und verwendenrandom_
(oder noch besser, speichern Sie einige Attributsuchen mitrandom_ = random.random
oderfrom random import random
). EsLOAD_FAST
ist eine schlechte Idee, bei jedem Funktionsaufruf den Leistungseinbruch eines Imports zu essen, nur um ihn zu verwenden .Dies hat einige Nachteile.
Testen
Wenn Sie Ihr Modul nicht durch Laufzeitänderungen testen möchten, wird es möglicherweise schwieriger. Anstatt zu tun
import mymodule mymodule.othermodule = module_stub
Du musst tun
import othermodule othermodule.foo = foo_stub
Dies bedeutet, dass Sie das andere Modul global patchen müssen, anstatt nur zu ändern, worauf die Referenz in mymodule verweist.
Abhängigkeitsverfolgung
Dies macht es nicht offensichtlich, von welchen Modulen Ihr Modul abhängt. Dies ist besonders ärgerlich, wenn Sie viele Bibliotheken von Drittanbietern verwenden oder Code neu organisieren.
Ich musste einen alten Code pflegen, der überall Inline-Importe verwendete. Dadurch war es äußerst schwierig, den Code umzugestalten oder neu zu verpacken.
Hinweise zur Leistung
Aufgrund der Art und Weise, wie Python Module zwischenspeichert, gibt es keinen Leistungseinbruch. Da sich das Modul im lokalen Namespace befindet, bietet der Import von Modulen in eine Funktion einen geringfügigen Leistungsvorteil.
Top Import
import random def f(): L = [] for i in xrange(1000): L.append(random.random()) for i in xrange(10000): f() $ time python test.py real 0m1.569s user 0m1.560s sys 0m0.010s
In Funktionskörper importieren
def f(): import random L = [] for i in xrange(1000): L.append(random.random()) for i in xrange(10000): f() $ time python test2.py real 0m1.385s user 0m1.380s sys 0m0.000s
quelle
Einige Probleme mit diesem Ansatz:
py2exe
,py2app
usw.Also ... der bevorzugte Weg ist, alle Importe oben in die Datei zu setzen. Ich habe festgestellt, dass wenn meine Importe schwer nachzuverfolgen sind, dies normalerweise bedeutet, dass ich zu viel Code habe, um ihn besser in zwei oder mehr Dateien aufzuteilen.
Einige Situationen , in denen ich habe die Einfuhren innerhalb von Funktionen gefunden , nützlich zu sein:
Außerdem: Das Einfügen von Importen in jede Funktion ist tatsächlich nicht wesentlich langsamer als am Anfang der Datei. Wenn jedes Modul zum ersten Mal geladen wird, wird es abgelegt
sys.modules
, und jeder nachfolgende Import kostet nur die Zeit zum Nachschlagen des Moduls, was ziemlich schnell ist (es wird nicht neu geladen).quelle
Ein weiterer nützlicher Hinweis ist, dass die
from module import *
Syntax innerhalb einer Funktion in Python 3.0 entfernt wurde.Es gibt eine kurze Erwähnung unter "Entfernte Syntax" hier:
http://docs.python.org/3.0/whatsnew/3.0.html
quelle
Ich würde vorschlagen, dass Sie versuchen,
from foo import bar
Importe zu vermeiden . Ich verwende sie nur in Paketen, in denen die Aufteilung in Module ein Implementierungsdetail darstellt und es sowieso nicht viele davon gibt.An allen anderen Stellen, an denen Sie ein Paket importieren, verwenden Sie es einfach
import foo
und referenzieren Sie es mit dem vollständigen Namenfoo.bar
. Auf diese Weise können Sie immer erkennen, woher ein bestimmtes Element stammt, und müssen die Liste der importierten Elemente nicht pflegen (in Wirklichkeit ist dies immer veraltet und importiert nicht mehr verwendete Elemente).Wenn
foo
es sich um einen wirklich langen Namen handelt, können Sie ihn vereinfachenimport foo as f
und dann schreibenf.bar
. Dies ist immer noch weitaus bequemer und expliziter als die Verwaltung allerfrom
Importe.quelle
Die Leute haben sehr gut erklärt, warum Inline-Importe vermieden werden sollten, aber nicht wirklich alternative Workflows, um die Gründe anzusprechen, aus denen Sie sie überhaupt wollen.
Um nach nicht verwendeten Importen zu suchen, verwende ich Pylint . Es führt eine statische (ish) -Analyse von Python-Code durch, und eines der (vielen) Dinge, auf die es prüft, sind nicht verwendete Importe. Zum Beispiel das folgende Skript ..
import urllib import urllib2 urllib.urlopen("http://stackoverflow.com")
..wurde die folgende Nachricht generieren:
example.py:2 [W0611] Unused import urllib2
Bei der Überprüfung der verfügbaren Importe verlasse ich mich im Allgemeinen auf die (ziemlich vereinfachte) Vervollständigung von TextMate. Wenn Sie die Esc-Taste drücken, wird das aktuelle Wort mit anderen im Dokument vervollständigt. Wenn ich fertig bin
import urllib
,urll[Esc]
wird erweitert aufurllib
, wenn nicht, springe ich zum Anfang der Datei und füge den Import hinzu.quelle
Ich glaube, dass dies in einigen Fällen / Szenarien ein empfohlener Ansatz ist. In Google App Engine wird beispielsweise das verzögerte Laden großer Module empfohlen, da dadurch die Aufwärmkosten für die Instanziierung neuer Python-VMs / -Interpreter minimiert werden. Schauen Sie sich die Präsentation eines Google Engineer an, in der dies beschrieben wird. Beachten Sie jedoch, dass dies nicht bedeutet, dass Sie alle Module faul laden sollten.
quelle
Unter dem Gesichtspunkt der Leistung können Sie Folgendes sehen: Sollten Python-Importanweisungen immer oben in einem Modul stehen?
Im Allgemeinen verwende ich nur lokale Importe, um Abhängigkeitszyklen zu unterbrechen.
quelle
Beide Varianten haben ihre Verwendung. In den meisten Fällen ist es jedoch besser, außerhalb der Funktionen zu importieren, nicht innerhalb dieser.
Performance
Es wurde in mehreren Antworten erwähnt, aber meiner Meinung nach fehlt allen eine vollständige Diskussion.
Wenn ein Modul zum ersten Mal in einen Python-Interpreter importiert wird, ist es langsam, unabhängig davon, ob es sich in der obersten Ebene oder in einer Funktion befindet. Es ist langsam, weil Python (ich konzentriere mich auf CPython, es könnte für andere Python-Implementierungen anders sein) mehrere Schritte ausführt:
__pycache__
Verzeichnis oder die.pyx
Dateien) konvertiert wurde, und wenn nicht, konvertiert es diese in Bytecode.sys.modules
.Nachfolgende Importe müssen nicht alle diese Aufgaben ausführen, da Python das Modul einfach von zurückgeben kann
sys.modules
. So werden nachfolgende Importe viel schneller sein.Es kann sein, dass eine Funktion in Ihrem Modul nicht sehr oft verwendet wird, dies hängt jedoch von einer Funktion ab
import
, die ziemlich lange dauert. Dann könnten Sie tatsächlich dasimport
Innere der Funktion verschieben. Dies beschleunigt den Import Ihres Moduls (da das lange Ladepaket nicht sofort importiert werden muss). Wenn die Funktion jedoch endgültig verwendet wird, ist sie beim ersten Aufruf langsam (da das Modul dann importiert werden muss). Dies kann sich auf die wahrgenommene Leistung auswirken, da Sie nicht alle Benutzer verlangsamen, sondern nur diejenigen, die die Funktion verwenden, die von der Abhängigkeit vom langsamen Laden abhängt.Die Suche in
sys.modules
ist jedoch nicht kostenlos. Es ist sehr schnell, aber nicht kostenlos. Wenn Sie also tatsächlich eine Funktion aufrufen, die einimport
Paket ist, werden Sie eine leicht verschlechterte Leistung feststellen:import random import itertools def func_1(): return random.random() def func_2(): import random return random.random() def loopy(func, repeats): for _ in itertools.repeat(None, repeats): func() %timeit loopy(func_1, 10000) # 1.14 ms ± 20.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit loopy(func_2, 10000) # 2.21 ms ± 138 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Das ist fast zweimal langsamer.
Es ist sehr wichtig zu erkennen, dass aaronasterling in der Antwort ein bisschen "betrogen" hat . Er erklärte, dass der Import in die Funktion die Funktion tatsächlich schneller macht. Und bis zu einem gewissen Grad ist dies wahr. Das liegt daran, wie Python nach Namen sucht:
Anstatt den lokalen Bereich und dann den globalen Bereich zu überprüfen, reicht es aus, den lokalen Bereich zu überprüfen, da der Name des Moduls im lokalen Bereich verfügbar ist. Das macht es tatsächlich schneller! Aber das ist eine Technik namens "Schleifeninvariante Codebewegung" . Dies bedeutet im Grunde, dass Sie den Overhead von etwas reduzieren, das in einer Schleife (oder wiederholt) ausgeführt wird, indem Sie es in einer Variablen vor der Schleife (oder den wiederholten Aufrufen) speichern. Anstatt
import
es in der Funktion zu verwenden, können Sie auch einfach eine Variable verwenden und sie dem globalen Namen zuweisen:import random import itertools def f1(repeats): "Repeated global lookup" for _ in itertools.repeat(None, repeats): random.random() def f2(repeats): "Import once then repeated local lookup" import random for _ in itertools.repeat(None, repeats): random.random() def f3(repeats): "Assign once then repeated local lookup" local_random = random for _ in itertools.repeat(None, repeats): local_random.random() %timeit f1(10000) # 588 µs ± 3.92 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit f2(10000) # 522 µs ± 1.95 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit f3(10000) # 527 µs ± 4.51 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Während Sie deutlich sehen können, dass wiederholte Suchvorgänge für das globale
random
Modul langsam sind, gibt es praktisch keinen Unterschied zwischen dem Importieren des Moduls in die Funktion oder dem Zuweisen des globalen Moduls in einer Variablen innerhalb der Funktion.Dies könnte bis zum Äußersten gehen, indem auch die Funktionssuche innerhalb der Schleife vermieden wird:
def f4(repeats): from random import random for _ in itertools.repeat(None, repeats): random() def f5(repeats): r = random.random for _ in itertools.repeat(None, repeats): r() %timeit f4(10000) # 364 µs ± 9.34 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit f5(10000) # 357 µs ± 2.73 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Wieder viel schneller, aber es gibt fast keinen Unterschied zwischen dem Import und der Variablen.
Optionale Abhängigkeiten
Manchmal kann ein Import auf Modulebene tatsächlich ein Problem sein. Wenn Sie beispielsweise keine weitere Abhängigkeit von der Installationszeit hinzufügen möchten, das Modul jedoch für einige zusätzliche Funktionen sehr hilfreich ist. Die Entscheidung, ob eine Abhängigkeit optional sein soll, sollte nicht leichtfertig getroffen werden, da dies die Benutzer betrifft (entweder wenn sie unerwartet sind
ImportError
oder auf andere Weise die "coolen Funktionen" verpassen) und die Installation des Pakets mit allen Funktionen für den Normalzustand komplizierter wird Abhängigkeitenpip
oderconda
(um nur zwei Paketmanager zu nennen) funktionieren sofort, aber für optionale Abhängigkeiten müssen die Benutzer Pakete später manuell installieren (es gibt einige Optionen, die es ermöglichen, die Anforderungen anzupassen, aber dann wieder die Last der Installation). richtig "wird auf den Benutzer gelegt).Aber auch dies könnte auf beide Arten geschehen:
try: import matplotlib.pyplot as plt except ImportError: pass def function_that_requires_matplotlib(): plt.plot()
oder:
def function_that_requires_matplotlib(): import matplotlib.pyplot as plt plt.plot()
Dies könnte angepasst werden, indem alternative Implementierungen bereitgestellt oder die Ausnahme (oder Nachricht) angepasst werden, die der Benutzer sieht. Dies ist jedoch der Hauptinhalt.
Der Top-Level-Ansatz könnte etwas besser sein, wenn man eine alternative "Lösung" für die optionale Abhängigkeit bereitstellen möchte, jedoch wird im Allgemeinen der In-Function-Import verwendet. Meistens, weil es zu einer saubereren Stapelspur führt und kürzer ist.
Zirkuläre Importe
In-Function-Importe können sehr hilfreich sein, um ImportErrors aufgrund von zirkulären Importen zu vermeiden. In vielen Fällen sind zirkuläre Importe ein Zeichen für eine "schlechte" Paketstruktur, aber wenn es absolut keine Möglichkeit gibt, einen zirkulären Import zu vermeiden, werden die "Kreise" (und damit die Probleme) gelöst, indem die Importe, die zum Kreis führen, hineingelegt werden die Funktionen, die es tatsächlich verwenden.
Wiederhole dich nicht
Wenn Sie tatsächlich alle Importe in die Funktion anstelle des Modulbereichs einfügen, wird Redundanz eingeführt, da Funktionen wahrscheinlich dieselben Importe erfordern. Das hat ein paar Nachteile:
Zusätzliche Gedanken:
Die meisten IDEs verfügen bereits über einen Prüfer für nicht verwendete Importe, sodass dies wahrscheinlich nur ein paar Klicks sind, um sie zu entfernen. Selbst wenn Sie keine IDE verwenden, können Sie gelegentlich ein statisches Code-Überprüfungsskript verwenden und es manuell reparieren. Eine andere Antwort erwähnte Pylint, aber es gibt andere (zum Beispiel Pyflakes).
Aus diesem Grund verwenden
__all__
und / oder definieren Sie normalerweise Ihre Funktionssubmodule und importieren nur die relevanten Klassen / Funktionen / ... in das Hauptmodul, z__init__.py
.Auch wenn Sie der Meinung sind, dass Sie den Modul-Namespace zu stark verschmutzt haben, sollten Sie wahrscheinlich in Betracht ziehen, das Modul in Submodule aufzuteilen. Dies ist jedoch nur für Dutzende von Importen sinnvoll.
Ein zusätzlicher (sehr wichtiger) Punkt, den Sie erwähnen sollten, wenn Sie die Verschmutzung durch Namespaces reduzieren möchten, ist die Vermeidung von
from module import *
Importen. Möglicherweise möchten Sie aber auchfrom module import a, b, c, d, e, ...
Importe vermeiden , die zu viele Namen importieren, und einfach das Modul importieren und mit auf die Funktionen zugreifenmodule.c
.Als letzten Ausweg können Sie immer Aliase verwenden, um zu vermeiden, dass der Namespace mit "öffentlichen" Importen verschmutzt wird, indem Sie Folgendes verwenden :
import random as _random
. Dadurch wird der Code schwerer zu verstehen, aber es wird sehr deutlich, was öffentlich sichtbar sein sollte und was nicht. Ich würde es nicht empfehlen, Sie sollten nur die__all__
Liste auf dem neuesten Stand halten (was der empfohlene und vernünftige Ansatz ist).Zusammenfassung
Die Auswirkungen auf die Leistung sind sichtbar, werden jedoch fast immer mikrooptimiert. Lassen Sie sich also nicht von Mikro-Benchmarks leiten, wo Sie die Importe platzieren. Außer wenn die Abhängigkeit zuerst sehr langsam ist
import
und nur für einen kleinen Teil der Funktionalität verwendet wird. Dann kann es für die meisten Benutzer tatsächlich einen sichtbaren Einfluss auf die wahrgenommene Leistung Ihres Moduls haben.Verwenden Sie die allgemein verständlichen Tools zum Definieren der öffentlichen API, ich meine die
__all__
Variable. Es mag etwas ärgerlich sein, es auf dem neuesten Stand zu halten, aber es überprüft auch alle Funktionen auf veraltete Importe oder wenn Sie eine neue Funktion hinzufügen, um alle relevanten Importe in dieser Funktion hinzuzufügen. Auf lange Sicht müssen Sie wahrscheinlich weniger Arbeit durch Aktualisierung erledigen__all__
.Es ist wirklich egal, welches Sie bevorzugen, beide arbeiten. Wenn Sie alleine arbeiten, können Sie über die Vor- und Nachteile nachdenken und das tun, was Sie für das Beste halten. Wenn Sie jedoch in einem Team arbeiten, sollten Sie sich wahrscheinlich an bekannte Muster halten (bei denen es sich um Importe auf höchster Ebene handelt
__all__
), da sie damit das tun können, was sie (wahrscheinlich) immer getan haben.quelle
Vielleicht möchten Sie einen Blick auf den Overhead der Importanweisung im Python-Wiki werfen . Kurz gesagt: Wenn das Modul bereits geladen wurde (siehe
sys.modules
), wird Ihr Code langsamer ausgeführt. Wenn Ihr Modul noch nicht geladen wurde undfoo
nur bei Bedarf geladen wird, was nullmal sein kann, ist die Gesamtleistung besser.quelle
Sicherheitsimplementierungen
Stellen Sie sich eine Umgebung vor, in der sich Ihr gesamter Python-Code in einem Ordner befindet, auf den nur ein privilegierter Benutzer Zugriff hat. Um zu vermeiden, dass Ihr gesamtes Programm als privilegierter Benutzer ausgeführt wird, beschließen Sie, Berechtigungen während der Ausführung einem nicht privilegierten Benutzer zu übertragen. Sobald Sie eine Funktion verwenden, die ein anderes Modul importiert, löst Ihr Programm eine aus,
ImportError
da der nicht privilegierte Benutzer das Modul aufgrund von Dateiberechtigungen nicht importieren kann.quelle