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, also jetzt statt
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)
Das macht ein paar Dinge. Erstens verseuche ich meine Module selten versehentlich mit dem Inhalt anderer Module. Ich könnte die Variable __all__
für das Modul festlegen, aber dann müsste ich sie aktualisieren, wenn sich das Modul weiterentwickelt, und dies trägt nicht zur Verschmutzung des Namespaces für Code bei, der tatsächlich im Modul enthalten ist.
Zweitens ende ich selten mit einer Fülle von Importen an der Spitze 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 im Funktionskörper vorhanden ist.
Die (zuvor) am häufigsten gewählte Antwort auf diese Frage ist schön formatiert, aber absolut falsch in Bezug auf die Leistung. Lass mich demonstrieren
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
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, kann es mehr effizient sein, das Modul in die Funktion zu importieren. Der Grund dafür ist einfach. Der Verweis wird von einem globalen Verweis auf einen lokalen Verweis verschoben. Dies bedeutet, dass der Compiler zumindest für CPython LOAD_FAST
-Anweisungen anstelle von LOAD_GLOBAL
-Anweisungen ausgibt. Diese sind, wie der Name schon sagt, schneller. Der andere Anrufbeantworter hat den Performance-Hit des Schauens in sys.modules
künstlich erhöht, indem er bei jeder einzelnen Iteration der Schleife importiert .
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 sind, dass man leichter verfolgen kann, wovon ein Modul abhängt, und dass dies mit den meisten anderen Bereichen des Python -Universums vereinbar ist.
Dies hat einige Nachteile.
Wenn Sie Ihr Modul während der Laufzeitänderung testen möchten, kann dies die Ausführung erschweren. Anstatt zu tun
import mymodule
mymodule.othermodule = module_stub
Du musst es tun
import othermodule
othermodule.foo = foo_stub
Das bedeutet, dass Sie das andere Modul global patchen müssen, statt nur das zu ändern, worauf die Referenz in mymodule verweist.
Daher ist es nicht offensichtlich, von welchen Modulen Ihr Modul abhängt. Dies ist besonders irritierend, wenn Sie viele Bibliotheken von Drittanbietern verwenden oder Code neu organisieren.
Ich musste einen alten Code beibehalten, bei dem Importe überall eingesetzt wurden. Dies machte es sehr schwierig, den Code zu refactorieren oder neu zu verpacken.
Aufgrund der Art und Weise, in der Python-Module zwischengespeichert werden, gibt es keinen Leistungseinbruch. Da sich das Modul im lokalen Namespace befindet, ergibt sich ein geringfügiger Leistungsvorteil beim Importieren von Modulen in einer Funktion.
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
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
Einige Probleme mit diesem Ansatz:
py2exe
, py2app
usw.Also ... der bevorzugte Weg ist, alle Importe an den Anfang der Datei zu setzen. Ich habe festgestellt, dass wenn meine Importe schwer zu verfolgen sind, das bedeutet, dass ich zu viel Code habe, der besser in zwei oder mehr Dateien aufgeteilt werden könnte.
Einige Situationen, in denen ich habe importierte Funktionen innerhalb von Funktionen als nützlich erachtet:
Außerdem: Das Einfügen von Importen in jede Funktion ist tatsächlich nicht merklich langsamer als am Anfang der Datei. Beim erstmaligen Laden jedes Moduls wird es in sys.modules
abgelegt, und jeder nachfolgende Import kostet nur die Zeit, um das Modul nachzuschlagen, was ziemlich schnell ist (es wird nicht neu geladen).
Eine weitere nützliche Sache ist, dass die from module import *
-Syntax innerhalb einer Funktion in Python 3.0 entfernt wurde.
Es gibt hier eine kurze Erwähnung unter "Entfernte Syntax":
Ich würde vorschlagen, dass Sie versuchen, from foo import bar
-Importe zu vermeiden. Ich verwende sie nur innerhalb von Paketen, bei denen die Aufteilung in Module ein Implementierungsdetail ist, von denen es ohnehin nicht viele gibt.
An allen anderen Stellen, an denen Sie ein Paket importieren, verwenden Sie einfach import foo
und referenzieren Sie es dann mit dem vollständigen Namen foo.bar
. Auf diese Weise können Sie immer erkennen, woher ein bestimmtes Element stammt, und müssen nicht die Liste der importierten Elemente verwalten (in der Realität ist dies immer veraltet und importieren Sie nicht mehr verwendete Elemente).
Wenn foo
ein wirklich langer Name ist, können Sie ihn mit import foo as f
vereinfachen und dann f.bar
schreiben. Dies ist immer noch viel praktischer und expliziter als die Aufrechterhaltung aller from
-Importe.
Die Leute haben sehr gut erklärt, warum man Inline-Importe vermeiden sollte, aber nicht wirklich alternative Workflows, um die Gründe zu berücksichtigen, aus denen man sie überhaupt möchte.
Es fällt mir schwer, Quelldateien nach oben und unten zu scrubben, um herauszufinden, welche Modulimporte verfügbar sind und so weiter
Um nicht verwendete Importe zu prüfen, 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 ungenutzte Importe. Zum Beispiel das folgende Skript ..
import urllib
import urllib2
urllib.urlopen("http://stackoverflow.com")
..wird folgende Meldung generiert:
example.py:2 [W0611] Unused import urllib2
Bei der Überprüfung der verfügbaren Importe verlasse ich mich im Allgemeinen auf die (relativ einfache) Fertigstellung von TextMate. Wenn Sie die Esc-Taste drücken, wird das aktuelle Word mit den anderen im Dokument vervollständigt. Wenn ich import urllib
getan habe, wird urll[Esc]
zu urllib
erweitert, andernfalls springe ich zum Anfang der Datei und füge den Import hinzu.
Vielleicht möchten Sie einen Blick auf Import Statement Overhead im Python-Wiki werfen. Kurz gesagt: Wenn das Modul bereits geladen wurde (siehe sys.modules
), wird der Code langsamer ausgeführt. Wenn Ihr Modul noch nicht geladen wurde und foo
nur bei Bedarf geladen wird, was Null sein kann, wird die Gesamtleistung verbessert.
Aus Performance-Sicht können Sie Folgendes sehen: Sollten sich Python-Importanweisungen immer an der Spitze eines Moduls befinden?
Im Allgemeinen verwende ich nur lokale Importe, um Abhängigkeitszyklen zu unterbrechen.
Ich glaube, dass dies in einigen Fällen/Szenarien ein empfohlener Ansatz ist. In Google App Engine wird zum Beispiel das langsame Laden großer Module empfohlen, da dadurch die Aufwärmkosten für das Instantiieren neuer Python-VMs/Interpreter minimiert werden. Schauen Sie sich eine Google Engineer's -Präsentation an, die dies beschreibt. Denken Sie jedoch daran, dass nicht bedeutet, dass Sie alle Ihre Module faul laden sollten.
Beide Varianten haben ihre Verwendung. In den meisten Fällen ist es jedoch besser, außerhalb der Funktionen zu importieren, nicht innerhalb von Funktionen.
Es wurde in mehreren Antworten erwähnt, aber meiner Meinung nach fehlt es an einer vollständigen Diskussion.
Wenn ein Modul zum ersten Mal in einen Python-Interpreter importiert wird, ist es langsam, unabhängig davon, ob es sich auf der obersten Ebene oder in einer Funktion befindet. Es ist langsam, weil Python (ich konzentriere mich auf CPython, es könnte sich bei anderen Python-Implementierungen unterscheiden), mehrere Schritte ausführt:
__pycache__
oder die Dateien .pyx
) konvertiert wurde, und konvertiert sie, falls nicht, in Bytecode.sys.modules
abgelegt.Nachfolgende Importe müssen dies nicht alle tun, da Python das Modul einfach von sys.modules
zurückgeben kann. Nachfolgende Importe werden also viel schneller sein.
Es kann sein, dass eine Funktion in Ihrem Modul nicht sehr oft verwendet wird, aber es hängt von einer import
ab, die ziemlich lange dauert. Dann könnten Sie tatsächlich die import
innerhalb der Funktion verschieben. Dadurch wird der Import Ihres Moduls schneller (da das langladende Paket 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 Auswirkungen auf die wahrgenommene Leistung haben, da Sie nicht alle Benutzer verlangsamen, sondern nur diejenigen verlangsamen, die die Funktion verwenden, die von der Abhängigkeit beim langsamen Laden abhängig ist.
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 import
s sehr häufig ein Paket ist, werden Sie eine etwas verschlechterte Leistung bemerken:
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 wissen, dass aaronasterling in der Antwort ein bisschen "geschummelt" hat . Er erklärte, dass der Import in der Funktion die Funktion tatsächlich beschleunigt. Und bis zu einem gewissen Grad trifft dies zu. Das ist, weil Python Namen nachschlägt:
Anstatt den lokalen Bereich und dann den globalen Bereich zu prü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, die "Loop-invariante Codebewegung" genannt wird. Das bedeutet im Wesentlichen, 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
ing in der Funktion, 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 erkennen können, dass wiederholte Suchvorgänge für die globale Variable random
langsam sind, gibt es praktisch keinen Unterschied zwischen dem Importieren des Moduls innerhalb der Funktion oder dem Zuweisen des globalen Moduls in einer Variablen innerhalb der Funktion.
Dies könnte ins Extreme 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.
Manchmal ist ein Import auf Modulebene tatsächlich ein Problem. Zum Beispiel, wenn Sie keine weitere Installationszeitabhängigkeit hinzufügen möchten, das Modul jedoch für einige Funktionen von additional hilfreich sein könnte. Die Entscheidung, ob eine Abhängigkeit optional sein sollte, sollte nicht leichtfertig getroffen werden, da die Benutzer davon betroffen sind (entweder wenn sie eine unerwartete ImportError
erhalten oder die "coolen Funktionen" anderweitig verpassen) und die Installation des Pakets mit allen Funktionen komplizierter wird, z normale Abhängigkeiten pip
oder conda
(um nur zwei Paketmanager zu erwähnen) funktionieren sofort, aber für optionale Abhängigkeiten müssen die Benutzer Pakete manuell nachinstallieren (es gibt einige Optionen, die es ermöglichen, die Anforderungen anzupassen, aber dann erneut die Last der Installation "richtig" wird dem Benutzer auferlegt).
Dies kann jedoch auch auf zwei Arten erfolgen:
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 kann durch die Bereitstellung alternativer Implementierungen oder das Anpassen der Ausnahme (oder Nachricht), die der Benutzer sieht, angepasst werden. Dies ist jedoch die Hauptübersicht.
Der Ansatz auf oberster Ebene könnte etwas besser sein, wenn eine alternative "Lösung" für die optionale Abhängigkeit bereitgestellt werden soll. In der Regel wird jedoch der In-Funktions-Import verwendet. Meistens weil es zu einem saubereren Stacktrace führt und kürzer ist.
In-Function-Importe können sehr hilfreich sein, um ImportErrors aufgrund von Umlaufimporten zu vermeiden. In vielen Fällen sind Zirkularimporte ein Zeichen für eine "schlechte" Paketstruktur, aber wenn es keinen Weg gibt, einen Zirkularimport zu vermeiden, wird der "Kreis" (und damit die Probleme) dadurch gelöst, dass die Importe, die zum Kreis führen, innen gesetzt werden die Funktionen, die es tatsächlich verwenden.
Wenn Sie tatsächlich alle Importe in die Funktion anstelle des Modulumfangs einfügen, führen Sie Redundanz ein, da Funktionen wahrscheinlich die gleichen Importe erfordern. Das hat einige Nachteile:
Ich ende selten mit einer Fülle von Importen an der Spitze meiner Module, von denen ich die Hälfte oder mehr nicht mehr benötige, weil ich sie überarbeitet habe.
Die meisten IDEs verfügen bereits über ein Kontrollkästchen für ungenutzte Importe. Daher sind wahrscheinlich nur wenige Klicks erforderlich, um sie zu entfernen. Selbst wenn Sie kein IDE verwenden, können Sie ab und zu ein Skript für die Überprüfung statischer Codes verwenden und es manuell beheben. Eine andere Antwort erwähnte Pylint, aber es gibt noch andere (zum Beispiel Pyflakes).
Ich versehe meine Module selten versehentlich mit dem Inhalt anderer Module
Deshalb verwenden Sie normalerweise __all__
und/oder definieren Ihre Funktionsmodule und importieren nur die relevanten Klassen/Funktionen/... im Hauptmodul, beispielsweise den __init__.py
.
Wenn Sie der Meinung sind, dass Sie den Modulnamensraum zu stark verschmutzt haben, sollten Sie das Modul möglicherweise in Submodule aufteilen. Dies ist jedoch nur für Dutzende von Importen sinnvoll.
Ein weiterer (sehr wichtiger) Punkt, der zu erwähnen ist, wenn Sie die Verschmutzung des Namespaces reduzieren möchten, ist das Vermeiden eines from module import *
-Imports. Möglicherweise möchten Sie jedoch auch from module import a, b, c, d, e, ...
-Importe vermeiden, die importieren zu viele Namen importieren, und nur das Modul importieren und mit module.c
auf die Funktionen zugreifen.
Als letzte Möglichkeit können Sie immer Aliase verwenden, um zu verhindern, dass der Namespace durch "öffentliche" Importe verschmutzt wird, indem Sie Folgendes verwenden: import random as _random
. Das macht den Code schwieriger zu verstehen, macht aber deutlich, was öffentlich sichtbar sein sollte und was nicht. Ich würde es nicht empfehlen, Sie sollten die Liste __all__
auf dem neuesten Stand halten (was der empfohlene und vernünftige Ansatz ist).
Die Auswirkungen auf die Leistung sind zwar sichtbar, werden jedoch fast immer mikrooptimierend sein. Lassen Sie also nicht die Entscheidung, wo Sie die Importe ablegen, von Mikro-Benchmarks abhängen. Außer, wenn die Abhängigkeit zuerst import
wirklich langsam ist und sie nur für einen kleinen Teil der Funktionalität verwendet wird. Dann kann es tatsächlich einen sichtbaren Einfluss auf die wahrgenommene Leistung Ihres Moduls für die meisten Benutzer haben.
Verwenden Sie die allgemein bekannten Tools zur Definition der öffentlichen API. Ich meine die Variable __all__
. Es ist vielleicht etwas ärgerlich, wenn Sie es auf dem neuesten Stand halten, aber auch alle Funktionen auf obsolete Importe prüfen oder wenn Sie eine neue Funktion hinzufügen, um alle relevanten Importe dieser Funktion hinzuzufügen. Auf lange Sicht müssen Sie wahrscheinlich weniger Arbeit erledigen, indem Sie __all__
aktualisieren.
Es ist wirklich egal, welchen 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 (was Importe auf oberster Ebene mit __all__
wäre), da sie es ihnen ermöglichen, das zu tun, was sie (wahrscheinlich) immer getan haben.