9.1 Erstellen von zirkulären Modulabhängigkeiten
Angenommen, du hast zwei Dateien, a.py
und b.py
, und jede importiert die andere wie folgt:
In a.py
:
import b
def f():
return b.x
print(f())
In b.py
:
import a
x = 1
def g():
print(a.f())
Zuerst versuchen wir, a.py
zu importieren:
import a
# 1
Hat super funktioniert. Vielleicht überrascht dich das. Schließlich importieren die Module einander zirkulär, und das sollte wahrscheinlich ein Problem sein, oder?
Die Antwort ist, dass das einfache Vorhandensein von zirkulären Modulimporten an sich kein Problem in Python ist. Wenn ein Modul bereits importiert wurde, ist Python klug genug, um nicht zu versuchen, es erneut zu importieren. Je nachdem, wann jedes Modul jedoch versucht, auf Funktionen oder Variablen zuzugreifen, die im anderen definiert sind, kann es tatsächlich zu Problemen kommen.
Zurück zu unserem Beispiel: Als wir a.py
importierten, hatte es kein Problem mit dem Import von b.py
, da b.py
nichts erfordert, damit irgendetwas aus a.py
während seines Imports definiert ist. Der einzige Verweis in b.py
auf a
ist der Aufruf von a.f()
. Aber dieser Aufruf befindet sich in g()
und nichts in a.py
oder b.py
ruft g()
auf. Also funktioniert alles reibungslos.
Aber was passiert, wenn wir versuchen, b.py
zu importieren (ohne vorher a.py
zu importieren, also):
import b
Traceback (most recent call last):
File "<stdin>", line 1, in
File "b.py", line 1, in
import a
File "a.py", line 6, in
print(f())
File "a.py", line 4, in f
return b.x
AttributeError: 'module' object has no attribute 'x'
Das Problem hier ist, dass b.py
versucht, a.py
zu importieren, das wiederum f()
aufruft, das versucht, auf b.x
zuzugreifen. Aber b.x
wurde noch nicht definiert. Daher das AttributeError
-Ausnahme.
Mindestens eine Lösung für dieses Problem ist ziemlich trivial. Ändere einfach b.py
, um a.py
innerhalb von g()
zu importieren:
x = 1
def g():
import a # Dies wird nur ausgewertet, wenn g() aufgerufen wird
print(a.f())
Jetzt läuft alles glatt, wenn wir es importieren:
import b
b.g()
# 1 Zum ersten Mal gedruckt, da Modul 'a' 'print(f())' am Ende aufruft
# 1 Zum zweiten Mal gedruckt, das ist unser Aufruf an 'g()'
9.2 Namenskollisionen mit Modulnamen der Python-Standardbibliothek
Eine der Freuden von Python ist die Vielzahl von Modulen, die "out-of-the-box" geliefert werden. Aber infolgedessen kann man ohne bewusst darauf zu achten, leicht auf Namenskonflikte zwischen deinem Modulnamen und einem Modulnamen aus der Standardbibliothek stoßen (zum Beispiel könntest du ein Modul namens email.py
in deinem Code haben, das mit einem gleichnamigen Modul der Standardbibliothek im Konflikt steht).
Dies kann zu ernsthaften Problemen führen. Wenn zum Beispiel ein Modul versucht, die Version des Moduls aus der Python-Standardbibliothek zu importieren, aber du ein Modul mit demselben Namen in deinem Projekt hast, wird es fälschlicherweise dein Modul anstelle des der Standardbibliothek importieren.
Deshalb solltest du darauf achten, keine Namen zu verwenden, die mit denen von Python-Standardbibliotheksmodulen übereinstimmen. Es ist viel einfacher, den Namen eines Moduls in deinem Projekt zu ändern, als eine Anfrage zur Namensänderung eines Standardbibliotheksmoduls einzureichen und dessen Genehmigung abzuwarten.
9.3 Sichtbarkeit von Ausnahmen
Betrachte die folgende Datei main.py
:
import sys
def bar(i):
if i == 1:
raise KeyError(1)
if i == 2:
raise ValueError(2)
def bad():
e = None
try:
bar(int("1"))
except KeyError as e:
print('key error')
except ValueError as e:
print('value error')
print(e)
bad()
Es sieht alles richtig aus, der Code sollte funktionieren, lass uns sehen, was er ausgibt:
$ python main.py 1
Traceback (most recent call last):
File "C:\Projects\Python\TinderBolt\main.py", line 19, in <module>
bad()
File "C:\Projects\Python\TinderBolt\main.py", line 17, in bad
print(e)
^
UnboundLocalError: cannot access local variable 'e' where it is not associated with a value
Was ist gerade passiert? Das Problem ist, dass in Python das Objekt im Ausnahmeblock außerhalb desselben nicht zugänglich ist. (Der Grund dafür ist, dass andernfalls die Objekte in diesem Block im Speicher gehalten würden, bis der Garbage Collector sie entfernt).
Eine Möglichkeit, dieses Problem zu umgehen, besteht darin, einen Verweis auf das Ausnahmeblockobjekt außerhalb dieses Blocks zu speichern, damit es zugänglich bleibt. Hier ist eine Version des vorherigen Beispiels, die diese Technik verwendet und den Code funktionsfähig macht:
import sys
def bar(i):
if i == 1:
raise KeyError(1)
if i == 2:
raise ValueError(2)
def good():
exception = None
try:
bar(int("1"))
except KeyError as e:
exception = e
print('key error')
except ValueError as e:
exception = e
print('value error')
print(exception)
good()
9.4 Falsche Nutzung der Methode __del__
Wenn der Interpreter ein Objekt löscht, prüft er, ob das Objekt über eine __del__
-Funktion verfügt, und wenn ja, ruft er diese vor dem Löschen des Objekts auf. Das ist sehr nützlich, wenn du möchtest, dass dein Objekt einige externe Ressourcen oder einen Cache bereinigt.
Angenommen, du hast eine Datei namens mod.py
:
import foo
class Bar(object):
...
def __del__(self):
foo.cleanup(self.myhandle)
Und du versuchst, so etwas aus einer anderen Datei another_mod.py
heraus zu machen:
import mod
mybar = mod.Bar()
Und bekommst einen schrecklichen AttributeError
.
Warum? Denn wie hier berichtet, wenn der Interpreter beendet wird, haben alle globalen Variablen des Moduls den Wert None
. Dadurch wird im obigen Beispiel zum Zeitpunkt des Aufrufs von __del__
der Name foo
bereits auf None
gesetzt.
Die Lösung dieses "Star-Problems" ist die Verwendung der speziellen Funktion atexit.register()
. Auf diese Weise werden bei der normalen Beendigung deines Programms (d.h. bei normalem Beenden) die handle's
entfernt, bevor der Interpreter beendet wird.
Mit dieser Berücksichtigung könnte die Korrektur für den obigen Code von mod.py
folgendermaßen aussehen:
import foo
import atexit
def cleanup(handle):
foo.cleanup(handle)
class Bar(object):
def __init__(self):
...
atexit.register(cleanup, self.myhandle)
Diese Implementierung bietet eine einfache und zuverlässige Möglichkeit, bei einer normalen Beendigung der Anwendung alle erforderlichen Bereinigungen auszuführen. Offensichtlich bleibt die Entscheidung, was mit dem Objekt zu tun ist, das mit dem Namen self.myhandle
verbunden ist, foo.cleanup
überlassen, aber ich denke, die Idee ist klar geworden.
GO TO FULL VERSION