FAQ zum Programmieren¶
Allgemeine Fragen¶
Gibt es einen Debugger auf Quellcode-Ebene mit Haltepunkten und Einzelschrittausführung?¶
Ja.
Unten sind mehrere Debugger für Python beschrieben, und die integrierte Funktion breakpoint() erlaubt dir, in jeden davon einzusteigen.
Das Modul pdb ist ein einfacher, aber ausreichender Debugger für Python im Konsolenbetrieb. Es ist Teil der Python-Standardbibliothek und im Referenzhandbuch der Bibliothek dokumentiert. Du kannst auch deinen eigenen Debugger schreiben und dir den Code von pdb als Beispiel nehmen.
Die interaktive Entwicklungsumgebung IDLE, die Teil der Standarddistribution von Python ist (normalerweise als idlelib verfügbar), enthält einen graphischen Debugger.
PythonWin ist eine Python-IDE mit einem graphischen Debugger auf Basis von pdb. Der Debugger von PythonWin färbt Haltepunkte ein und hat eine Reihe praktischer Eigenschaften, etwa das Debuggen von Programmen außerhalb von PythonWin. PythonWin ist als Teil des Projekts pywin32 und als Teil der Distribution ActivePython verfügbar.
Eric ist eine IDE, die auf PyQt und der Editor-Komponente Scintilla aufbaut.
trepan3k ist ein Debugger im Stil von gdb.
Visual Studio Code ist eine IDE mit Debugging-Werkzeugen, die sich in Versionsverwaltungen integriert.
Es gibt eine Reihe kommerzieller Python-IDEs mit graphischen Debuggern. Dazu gehören:
Gibt es Werkzeuge, die beim Finden von Fehlern oder bei statischer Analyse helfen?¶
Ja.
Ruff, Pylint und Pyflakes führen grundlegende Prüfungen durch, mit denen du Fehler früher findest.
Statische Typprüfer wie mypy, ty, Pyrefly und pytype können Typhinweise im Python-Quellcode prüfen.
Wie erzeuge ich aus einem Python-Skript eine eigenständige Binärdatei?¶
Du brauchst Python nicht nach C-Code kompilieren zu können, wenn du nur ein eigenständiges Programm willst, das Nutzer herunterladen und ausführen können, ohne vorher die Python-Distribution zu installieren. Es gibt eine Reihe von Werkzeugen, die den von einem Programm benötigten Satz an Modulen bestimmen und diese Module mit einer Python-Binärdatei zu einer einzigen ausführbaren Datei zusammenbinden.
Eines davon ist das Werkzeug freeze, das im Python-Quellbaum unter Tools/freeze enthalten ist. Es wandelt Python-Bytecode in C-Arrays um. Mit einem C-Compiler kannst du dann alle deine Module in ein neues Programm einbetten, das anschließend mit den Standardmodulen von Python gelinkt wird.
Es arbeitet so, dass es deinen Quellcode rekursiv nach Import-Anweisungen durchsucht (in beiden Formen) und die Module sowohl im Standardsuchpfad von Python als auch im Quellverzeichnis sucht (für integrierte Module). Anschließend wandelt es den Bytecode der in Python geschriebenen Module in C-Code um (Array-Initialisierer, die sich mit dem Modul marshal in Codeobjekte verwandeln lassen) und erzeugt eine maßgeschneiderte Konfigurationsdatei, die nur jene integrierten Module enthält, die im Programm tatsächlich verwendet werden. Dann kompiliert es den erzeugten C-Code und linkt ihn mit dem Rest des Python-Interpreters zu einer eigenständigen Binärdatei, die sich genau wie dein Skript verhält.
Die folgenden Pakete helfen beim Erzeugen von ausführbaren Dateien für Konsole und graphische Oberfläche:
Nuitka (plattformübergreifend)
PyInstaller (plattformübergreifend)
PyOxidizer (plattformübergreifend)
cx_Freeze (plattformübergreifend)
py2app (nur macOS)
py2exe (nur Windows)
Gibt es Kodierungsstandards oder einen Stilleitfaden für Python-Programme?¶
Ja. Der für Module der Standardbibliothek verlangte Programmierstil ist in PEP 8 dokumentiert.
Kernsprache¶
Warum erhalte ich einen UnboundLocalError, obwohl die Variable einen Wert hat?¶
Es kann überraschen, in vorher funktionierendem Code einen UnboundLocalError zu erhalten, nachdem er durch eine Zuweisung irgendwo im Rumpf einer Funktion verändert wurde.
Dieser Code:
>>> x = 10
>>> def bar():
... print(x)
...
>>> bar()
10
funktioniert, aber dieser Code:
>>> x = 10
>>> def foo():
... print(x)
... x += 1
führt zu einem UnboundLocalError:
>>> foo()
Traceback (most recent call last):
...
UnboundLocalError: cannot access local variable 'x' where it is not associated with a value
Das liegt daran, dass eine Variable, der du in einem Gültigkeitsbereich etwas zuweist, lokal zu diesem Bereich wird und jede gleichnamige Variable im äußeren Bereich verdeckt. Da die letzte Anweisung in foo x einen neuen Wert zuweist, erkennt der Compiler x als lokale Variable. Folglich versucht das vorangehende print(x), die nicht initialisierte lokale Variable auszugeben, und es kommt zu einem Fehler.
Im Beispiel oben kannst du auf die Variable des äußeren Bereichs zugreifen, indem du sie als global deklarierst:
>>> x = 10
>>> def foobar():
... global x
... print(x)
... x += 1
...
>>> foobar()
10
Diese ausdrückliche Deklaration ist nötig, um dich daran zu erinnern, dass du (anders als in der oberflächlich ähnlichen Situation mit Klassen- und Instanzvariablen) tatsächlich den Wert der Variablen im äußeren Bereich änderst:
>>> print(x)
11
Etwas Ähnliches kannst du in einem verschachtelten Gültigkeitsbereich mit dem Schlüsselwort nonlocal tun:
>>> def foo():
... x = 10
... def bar():
... nonlocal x
... print(x)
... x += 1
... bar()
... print(x)
...
>>> foo()
10
11
Welche Regeln gelten in Python für lokale und globale Variablen?¶
In Python sind Variablen, die innerhalb einer Funktion nur gelesen werden, implizit global. Wird einer Variablen irgendwo im Rumpf der Funktion ein Wert zugewiesen, gilt sie als lokal, sofern sie nicht ausdrücklich als global deklariert ist.
Das überrascht zunächst etwas, erklärt sich aber nach kurzem Nachdenken. Zum einen schützt die Pflicht, global für zugewiesene Variablen anzugeben, vor unbeabsichtigten Nebenwirkungen. Zum anderen müsstest du global ständig verwenden, wenn es für alle globalen Zugriffe nötig wäre. Du müsstest jeden Zugriff auf eine integrierte Funktion oder auf einen Bestandteil eines importierten Moduls als global deklarieren. Dieses Durcheinander würde den Nutzen der global-Deklaration zum Erkennen von Nebenwirkungen zunichtemachen.
Warum geben Lambdas, die in einer Schleife mit verschiedenen Werten definiert wurden, alle dasselbe Ergebnis zurück?¶
Nimm an, du definierst mit einer for-Schleife einige verschiedene Lambdas (oder auch gewöhnliche Funktionen), zum Beispiel:
>>> squares = []
>>> for x in range(5):
... squares.append(lambda: x**2)
Das ergibt eine Liste mit 5 Lambdas, die x**2 berechnen. Du würdest erwarten, dass sie beim Aufruf jeweils 0, 1, 4, 9 und 16 zurückgeben. Wenn du es aber ausprobierst, siehst du, dass alle 16 zurückgeben:
>>> squares[2]()
16
>>> squares[4]()
16
Das passiert, weil x nicht lokal zu den Lambdas ist, sondern im äußeren Gültigkeitsbereich definiert wird, und weil darauf beim Aufruf des Lambdas zugegriffen wird — nicht bei seiner Definition. Am Ende der Schleife ist der Wert von x gleich 4, deshalb geben jetzt alle Funktionen 4**2 zurück, also 16. Du kannst das auch prüfen, indem du den Wert von x änderst und siehst, wie sich die Ergebnisse der Lambdas ändern:
>>> x = 8
>>> squares[2]()
64
Um das zu vermeiden, musst du die Werte in Variablen speichern, die lokal zu den Lambdas sind, damit sie nicht vom Wert des globalen x abhängen:
>>> squares = []
>>> for x in range(5):
... squares.append(lambda n=x: n**2)
Hier erzeugt n=x eine neue Variable n, die lokal zum Lambda ist und bei der Definition des Lambdas berechnet wird, sodass sie denselben Wert hat, den x an dieser Stelle der Schleife hatte. Das bedeutet, dass der Wert von n im ersten Lambda 0 ist, im zweiten 1, im dritten 2 und so weiter. Deshalb gibt jedes Lambda jetzt das richtige Ergebnis zurück:
>>> squares[2]()
4
>>> squares[4]()
16
Beachte, dass dieses Verhalten nicht auf Lambdas beschränkt ist, sondern genauso für gewöhnliche Funktionen gilt.
Was sind die „besten Vorgehensweisen“ für die Verwendung von import in einem Modul?¶
Verwende im Allgemeinen nicht from modulename import *. Das überfüllt den Namensraum des Importierenden und macht es Lintern deutlich schwerer, undefinierte Namen zu erkennen.
Importiere Module am Anfang einer Datei. Das macht deutlich, welche anderen Module dein Code benötigt, und erspart die Frage, ob der Modulname im Gültigkeitsbereich liegt. Ein Import pro Zeile macht es einfach, Modulimporte hinzuzufügen und zu löschen, mehrere Importe pro Zeile brauchen dagegen weniger Platz auf dem Bildschirm.
Es ist gute Praxis, Module in der folgenden Reihenfolge zu importieren:
Module von Fremdbibliotheken (alles, was im Verzeichnis site-packages von Python installiert ist) – etwa dateutil, requests, tzdata
selbst entwickelte Module
Manchmal ist es nötig, Importe in eine Funktion oder Klasse zu verschieben, um Probleme mit zirkulären Importen zu vermeiden. Gordon McMillan sagt dazu:
Zirkuläre Importe sind in Ordnung, wenn beide Module die Form „import <module>“ verwenden. Sie scheitern, wenn das zweite Modul einen Namen aus dem ersten holen will („from module import name“) und der Import auf der obersten Ebene steht. Das liegt daran, dass die Namen im ersten Modul noch nicht verfügbar sind, weil das erste Modul gerade damit beschäftigt ist, das zweite zu importieren.
Wird das zweite Modul in diesem Fall nur in einer Funktion verwendet, lässt sich der Import leicht in diese Funktion verschieben. Wenn der Import dann aufgerufen wird, hat das erste Modul seine Initialisierung abgeschlossen, und das zweite Modul kann seinen Import durchführen.
Es kann außerdem nötig sein, Importe aus der obersten Ebene des Codes herauszuholen, wenn einige der Module plattformspezifisch sind. In diesem Fall ist es womöglich gar nicht möglich, alle Module am Anfang der Datei zu importieren. Dann ist es eine gute Lösung, die richtigen Module im jeweils zugehörigen plattformspezifischen Code zu importieren.
Verschiebe Importe nur dann in einen lokalen Gültigkeitsbereich, etwa in eine Funktionsdefinition, wenn es nötig ist, um ein Problem wie einen zirkulären Import zu lösen, oder wenn du die Initialisierungszeit eines Moduls verkürzen willst. Diese Technik hilft besonders dann, wenn viele der Importe je nach Programmablauf gar nicht gebraucht werden. Vielleicht willst du Importe auch dann in eine Funktion verschieben, wenn die Module ausschließlich in dieser Funktion verwendet werden. Beachte, dass das erste Laden eines Moduls wegen der einmaligen Initialisierung teuer sein kann, ein Modul mehrfach zu laden aber praktisch kostenlos ist und nur ein paar Suchvorgänge im Dictionary kostet. Selbst wenn der Modulname den Gültigkeitsbereich verlassen hat, ist das Modul wahrscheinlich noch über sys.modules verfügbar.
Wie übergebe ich optionale Parameter oder Schlüsselwortparameter von einer Funktion an eine andere?¶
Sammle die Argumente mit den Angaben * und ** in der Parameterliste der Funktion ein. Das gibt dir die Positionsargumente als Tupel und die Schlüsselwortargumente als Dictionary. Diese Argumente kannst du dann beim Aufruf einer anderen Funktion mit * und ** weitergeben:
def f(x, *args, **kwargs):
...
kwargs['width'] = '14.3c'
...
g(x, *args, **kwargs)
Was ist der Unterschied zwischen Argumenten und Parametern?¶
Parameter werden durch die Namen festgelegt, die in einer Funktionsdefinition auftauchen, während Argumente die Werte sind, die beim Aufruf tatsächlich an eine Funktion übergeben werden. Parameter legen fest, welche Art von Argumenten eine Funktion annehmen kann. Betrachte zum Beispiel die folgende Funktionsdefinition:
def func(foo, bar=None, **kwargs):
pass
foo, bar und kwargs sind Parameter von func. Beim Aufruf von func, zum Beispiel:
func(42, bar=314, extra=somevar)
sind die Werte 42, 314 und somevar dagegen Argumente.
Warum hat das Ändern von Liste ‚y‘ auch Liste ‚x‘ verändert?¶
Wenn du Code wie diesen geschrieben hast:
>>> x = []
>>> y = x
>>> y.append(10)
>>> y
[10]
>>> x
[10]
fragst du dich vielleicht, warum das Anhängen eines Elements an y auch x verändert hat.
Zwei Dinge führen zu diesem Ergebnis:
Variablen sind einfach Namen, die auf Objekte verweisen.
y = xerzeugt keine Kopie der Liste – es erzeugt eine neue Variabley, die auf dasselbe Objekt verweist wiex. Das bedeutet, dass es nur ein Objekt gibt, nämlich die Liste, und dassxundybeide darauf verweisen.Listen sind veränderlich, das heißt, du kannst ihren Inhalt ändern.
Nach dem Aufruf von append() hat sich der Inhalt des veränderlichen Objekts von [] zu [10] geändert. Da beide Variablen auf dasselbe Objekt verweisen, greift man über jeden der beiden Namen auf den geänderten Wert [10] zu.
Wenn wir x stattdessen ein unveränderliches Objekt zuweisen:
>>> x = 5 # ints sind unveränderlich
>>> y = x
>>> x = x + 1 # 5 lässt sich nicht verändern, hier wird ein neues Objekt erzeugt
>>> x
6
>>> y
5
sehen wir, dass x und y in diesem Fall nicht mehr gleich sind. Das liegt daran, dass ganze Zahlen unveränderlich sind: Mit x = x + 1 verändern wir nicht den int 5, indem wir seinen Wert erhöhen, sondern wir erzeugen ein neues Objekt, nämlich den int 6, und weisen es x zu, ändern also, auf welches Objekt x verweist. Nach dieser Zuweisung haben wir zwei Objekte, die ints 6 und 5, und zwei Variablen, die darauf verweisen: x verweist jetzt auf 6, y aber noch auf 5.
Einige Operationen verändern das Objekt (zum Beispiel y.append(10) und y.sort()), während oberflächlich ähnliche Operationen ein neues Objekt erzeugen (zum Beispiel y = y + [10] und sorted(y)). In Python gibt eine Methode, die ein Objekt verändert, im Allgemeinen None zurück — in der Standardbibliothek ausnahmslos —, damit die beiden Arten von Operationen nicht verwechselt werden. Schreibst du also versehentlich y.sort() in der Annahme, eine sortierte Kopie von y zu erhalten, bekommst du stattdessen None, was dein Programm wahrscheinlich einen leicht zu diagnostizierenden Fehler erzeugen lässt.
Es gibt allerdings eine Klasse von Operationen, bei der dieselbe Operation sich je nach Typ unterschiedlich verhält: die erweiterten Zuweisungsoperatoren. So verändert += Listen, aber nicht Tupel oder ints (a_list += [1, 2, 3] entspricht a_list.extend([1, 2, 3]) und verändert a_list, während some_tuple += (1, 2, 3) und some_int += 1 neue Objekte erzeugen).
Mit anderen Worten:
Haben wir ein veränderliches Objekt (etwa
list,dict,set), können wir es mit bestimmten Operationen verändern, und alle Variablen, die darauf verweisen, sehen die Änderung.Haben wir ein unveränderliches Objekt (etwa
str,int,tuple), sehen alle Variablen, die darauf verweisen, immer denselben Wert. Operationen, die diesen Wert in einen neuen Wert überführen, geben dann aber stets ein neues Objekt zurück.
Wenn du wissen willst, ob zwei Variablen auf dasselbe Objekt verweisen, kannst du den Operator is oder die integrierte Funktion id() verwenden.
Wie schreibe ich eine Funktion mit Ausgabeparametern (call by reference)?¶
Denk daran, dass Argumente in Python per Zuweisung übergeben werden. Da eine Zuweisung lediglich Referenzen auf Objekte erzeugt, gibt es keine Verbindung zwischen einem Argumentnamen beim Aufrufer und beim Aufgerufenen und folglich kein call by reference. Den gewünschten Effekt kannst du auf mehrere Weisen erreichen.
Indem du ein Tupel der Ergebnisse zurückgibst:
>>> def func1(a, b): ... a = 'new-value' # a und b sind lokale Namen ... b = b + 1 # neuen Objekten zugewiesen ... return a, b # neue Werte zurückgeben ... >>> x, y = 'old-value', 99 >>> func1(x, y) ('new-value', 100)
Das ist fast immer die klarste Lösung.
Indem du globale Variablen verwendest. Das ist nicht thread-sicher und nicht empfehlenswert.
Indem du ein veränderliches (an seinem Platz änderbares) Objekt übergibst:
>>> def func2(a): ... a[0] = 'new-value' # 'a' verweist auf eine veränderliche Liste ... a[1] = a[1] + 1 # ändert ein gemeinsam genutztes Objekt ... >>> args = ['old-value', 99] >>> func2(args) >>> args ['new-value', 100]
Indem du ein Dictionary übergibst, das verändert wird:
>>> def func3(args): ... args['a'] = 'new-value' # args ist ein veränderliches Dictionary ... args['b'] = args['b'] + 1 # es an seinem Platz ändern ... >>> args = {'a': 'old-value', 'b': 99} >>> func3(args) >>> args {'a': 'new-value', 'b': 100}
Oder indem du die Werte in einer Klasseninstanz zusammenfasst:
>>> class Namespace: ... def __init__(self, /, **args): ... for key, value in args.items(): ... setattr(self, key, value) ... >>> def func4(args): ... args.a = 'new-value' # args ist ein veränderlicher Namespace ... args.b = args.b + 1 # Objekt an seinem Platz ändern ... >>> args = Namespace(a='old-value', b=99) >>> func4(args) >>> vars(args) {'a': 'new-value', 'b': 100}
Es gibt fast nie einen guten Grund, so kompliziert zu werden.
Deine beste Wahl ist, ein Tupel mit den mehreren Ergebnissen zurückzugeben.
Wie schreibt man in Python eine Funktion höherer Ordnung?¶
Du hast zwei Möglichkeiten: Du kannst verschachtelte Gültigkeitsbereiche verwenden oder aufrufbare Objekte. Nimm zum Beispiel an, du wolltest linear(a,b) definieren, das eine Funktion f(x) zurückgibt, die den Wert a*x+b berechnet. Mit verschachtelten Gültigkeitsbereichen:
def linear(a, b):
def result(x):
return a * x + b
return result
Oder mit einem aufrufbaren Objekt:
class linear:
def __init__(self, a, b):
self.a, self.b = a, b
def __call__(self, x):
return self.a * x + self.b
In beiden Fällen ergibt
taxes = linear(0.3, 2)
ein aufrufbares Objekt, bei dem taxes(10e6) == 0.3 * 10e6 + 2 gilt.
Der Weg über ein aufrufbares Objekt hat den Nachteil, dass er etwas langsamer ist und zu leicht längerem Code führt. Beachte aber, dass sich eine Sammlung aufrufbarer Objekte ihre Signatur über Vererbung teilen kann:
class exponential(linear):
# __init__ wird geerbt
def __call__(self, x):
return self.a * (x ** self.b)
Objekte können Zustand für mehrere Methoden kapseln:
class counter:
value = 0
def set(self, x):
self.value = x
def up(self):
self.value = self.value + 1
def down(self):
self.value = self.value - 1
count = counter()
inc, dec, reset = count.up, count.down, count.set
Hier verhalten sich inc(), dec() und reset() wie Funktionen, die sich dieselbe Zählvariable teilen.
Wie kopiere ich in Python ein Objekt?¶
Versuche im allgemeinen Fall copy.copy() oder copy.deepcopy(). Nicht alle Objekte lassen sich kopieren, die meisten aber doch.
Manche Objekte lassen sich leichter kopieren. Dictionarys haben eine Methode copy():
newdict = olddict.copy()
Sequenzen lassen sich durch Slicing kopieren:
new_l = l[:]
Wie finde ich die Methoden oder Attribute eines Objekts?¶
Für eine Instanz x einer selbst definierten Klasse gibt dir(x) eine alphabetisch sortierte Liste der Namen zurück, die die Instanzattribute sowie die von ihrer Klasse festgelegten Methoden und Attribute enthält.
Wie kann mein Code den Namen eines Objekts herausfinden?¶
Im Allgemeinen kann er das nicht, weil Objekte eigentlich keine Namen haben. Im Grunde bindet eine Zuweisung immer einen Namen an einen Wert. Dasselbe gilt für def- und class-Anweisungen, nur ist der Wert dort ein aufrufbares Objekt. Sieh dir den folgenden Code an:
>>> class A:
... pass
...
>>> B = A
>>> a = B()
>>> b = a
>>> print(b)
<__main__.A object at 0x16D07CC>
>>> print(a)
<__main__.A object at 0x16D07CC>
Man kann durchaus sagen, dass die Klasse einen Namen hat: Obwohl sie an zwei Namen gebunden ist und über den Namen B aufgerufen wird, wird die erzeugte Instanz weiterhin als Instanz der Klasse A gemeldet. Es ist aber unmöglich zu sagen, ob der Name der Instanz a oder b ist, da beide Namen an denselben Wert gebunden sind.
Im Allgemeinen sollte es nicht nötig sein, dass dein Code die Namen bestimmter Werte „kennt“. Sofern du nicht absichtlich introspektive Programme schreibst, ist das meist ein Hinweis darauf, dass ein anderer Ansatz von Vorteil wäre.
Fredrik Lundh hat in comp.lang.python einmal einen hervorragenden Vergleich als Antwort auf diese Frage gegeben:
Genauso, wie du den Namen der Katze herausfindest, die du auf deiner Veranda gefunden hast: Die Katze (das Objekt) selbst kann dir ihren Namen nicht sagen, und es ist ihr auch ziemlich gleichgültig – der einzige Weg herauszufinden, wie sie heißt, ist also, alle deine Nachbarn (die Namensräume) zu fragen, ob es ihre Katze (ihr Objekt) ist …
… und sei nicht überrascht, wenn du feststellst, dass sie unter vielen Namen bekannt ist — oder unter gar keinem!
Was ist mit dem Vorrang des Komma-Operators?¶
Das Komma ist in Python kein Operator. Sieh dir diese Sitzung an:
>>> "a" in "b", "a"
(False, 'a')
Da das Komma kein Operator ist, sondern ein Trennzeichen zwischen Ausdrücken, wird das Obige so ausgewertet, als hättest du eingegeben:
("a" in "b"), "a"
und nicht:
"a" in ("b", "a")
Dasselbe gilt für die verschiedenen Zuweisungsoperatoren (=, += und so weiter). Sie sind keine echten Operatoren, sondern syntaktische Trennzeichen in Zuweisungsanweisungen.
Gibt es eine Entsprechung zum ternären Operator „?:“ aus C?¶
Ja, die gibt es. Die Syntax lautet so:
[on_true] if [expression] else [on_false]
x, y = 50, 25
small = x if x < y else y
Bevor diese Syntax in Python 2.5 eingeführt wurde, war es üblich, logische Operatoren zu verwenden:
[expression] and [on_true] or [on_false]
Diese Schreibweise ist allerdings unsicher, denn sie kann falsche Ergebnisse liefern, wenn on_true einen falschen booleschen Wert hat. Deshalb ist die Form ... if ... else ... immer die bessere Wahl.
Kann man in Python unverständliche Einzeiler schreiben?¶
Ja. Üblicherweise geschieht das, indem man lambda in lambda verschachtelt. Sieh dir die folgenden drei Beispiele an, leicht angepasst nach Ulf Bartelt:
from functools import reduce
# Primes < 1000
print(list(filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0,
map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,1000)))))
# First 10 Fibonacci numbers
print(list(map(lambda x,f=lambda x,f:(f(x-1,f)+f(x-2,f)) if x>1 else 1:
f(x,f), range(10))))
# Mandelbrot set
print((lambda Ru,Ro,Iu,Io,IM,Sx,Sy:reduce(lambda x,y:x+'\n'+y,map(lambda y,
Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,Sy=Sy,L=lambda yc,Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,i=IM,
Sx=Sx,Sy=Sy:reduce(lambda x,y:x+y,map(lambda x,xc=Ru,yc=yc,Ru=Ru,Ro=Ro,
i=i,Sx=Sx,F=lambda xc,yc,x,y,k,f=lambda xc,yc,x,y,k,f:(k<=0)or (x*x+y*y
>=4.0) or 1+f(xc,yc,x*x-y*y+xc,2.0*x*y+yc,k-1,f):f(xc,yc,x,y,k,f):chr(
64+F(Ru+x*(Ro-Ru)/Sx,yc,0,0,i)),range(Sx))):L(Iu+y*(Io-Iu)/Sy),range(Sy
))))(-2.1, 0.7, -1.2, 1.2, 30, 80, 24))
# \___ ___/ \___ ___/ | | |__ lines on screen
# V V | |______ columns on screen
# | | |__________ maximum of "iterations"
# | |_________________ range on y axis
# |____________________________ range on x axis
Kinder, macht das nicht zu Hause nach!
Was bedeutet der Schrägstrich (/) in der Parameterliste einer Funktion?¶
Ein Schrägstrich in der Argumentliste einer Funktion zeigt an, dass die Parameter davor rein positionsbezogen sind. Rein positionsbezogene Parameter sind diejenigen ohne von außen verwendbaren Namen. Beim Aufruf einer Funktion mit rein positionsbezogenen Parametern werden Argumente ausschließlich anhand ihrer Position auf Parameter abgebildet. divmod() ist zum Beispiel eine Funktion, die rein positionsbezogene Parameter annimmt. Ihre Dokumentation sieht so aus:
>>> help(divmod)
Help on built-in function divmod in module builtins:
divmod(x, y, /)
Return the tuple (x//y, x%y). Invariant: div*y + mod == x.
Der Schrägstrich am Ende der Parameterliste bedeutet, dass beide Parameter rein positionsbezogen sind. divmod() mit Schlüsselwortargumenten aufzurufen führt deshalb zu einem Fehler:
>>> divmod(x=3, y=4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: divmod() takes no keyword arguments
Zahlen und Zeichenketten¶
Wie gebe ich hexadezimale und oktale ganze Zahlen an?¶
Um eine Oktalzahl anzugeben, stelle dem oktalen Wert eine Null und dann ein kleines oder großes „o“ voran. Um zum Beispiel die Variable „a“ auf den oktalen Wert „10“ zu setzen (8 im Dezimalsystem), gib ein:
>>> a = 0o10
>>> a
8
Hexadezimal ist genauso einfach. Stelle der Hexadezimalzahl einfach eine Null und dann ein kleines oder großes „x“ voran. Die Hexadezimalziffern selbst dürfen klein oder groß geschrieben werden. Zum Beispiel im Python-Interpreter:
>>> a = 0xa5
>>> a
165
>>> b = 0XB2
>>> b
178
Warum gibt -22 // 10 den Wert -3 zurück?¶
Ausschlaggebend ist vor allem der Wunsch, dass i % j dasselbe Vorzeichen wie j hat. Wenn du das willst und außerdem willst, dass:
i == (i // j) * j + (i % j)
dann muss die Ganzzahldivision abrunden. C verlangt diese Identität ebenfalls, und deshalb müssen Compiler, die i // j abschneiden, dafür sorgen, dass i % j dasselbe Vorzeichen wie i hat.
Es gibt kaum echte Anwendungsfälle für i % j, wenn j negativ ist. Ist j positiv, gibt es viele, und in praktisch allen davon ist es nützlicher, wenn i % j den Wert >= 0 hat. Wenn die Uhr jetzt 10 zeigt, was zeigte sie vor 200 Stunden? -190 % 12 == 2 ist nützlich, -190 % 12 == -10 ist ein Fehler, der nur darauf wartet, zuzuschlagen.
Wie komme ich an das Attribut eines int-Literals, ohne einen SyntaxError zu bekommen?¶
Das Attribut eines int-Literals auf dem gewohnten Weg abzufragen ergibt einen SyntaxError, weil der Punkt als Dezimalpunkt gelesen wird:
>>> 1.__class__
File "<stdin>", line 1
1.__class__
^
SyntaxError: invalid decimal literal
Die Lösung ist, das Literal durch ein Leerzeichen oder Klammern vom Punkt zu trennen.
>>> 1 .__class__
<class 'int'>
>>> (1).__class__
<class 'int'>
Wie wandle ich eine Zeichenkette in eine Zahl um?¶
Verwende für ganze Zahlen den integrierten Typkonstruktor int(), zum Beispiel int('144') == 144. Entsprechend wandelt float() in eine Gleitkommazahl um, zum Beispiel float('144') == 144.0.
Voreingestellt deuten diese die Zahl als Dezimalzahl, sodass int('0144') == 144 gilt und int('0x144') einen ValueError auslöst. int(string, base) nimmt als zweites, optionales Argument die Basis, aus der umgewandelt wird, also int( '0x144', 16) == 324. Ist die Basis als 0 angegeben, wird die Zahl nach den Regeln von Python gedeutet: ein führendes ‚0o‘ steht für oktal, ‚0x‘ für eine Hexadezimalzahl.
Verwende nicht die integrierte Funktion eval(), wenn du nur Zeichenketten in Zahlen umwandeln willst. eval() ist deutlich langsamer und stellt ein Sicherheitsrisiko dar: Jemand könnte dir einen Python-Ausdruck übergeben, der unerwünschte Nebenwirkungen hat. Zum Beispiel könnte jemand __import__('os').system("rm -rf $HOME") übergeben, was dein Benutzerverzeichnis löschen würde.
eval() hat außerdem den Effekt, Zahlen als Python-Ausdrücke zu deuten, sodass zum Beispiel eval('09') einen Syntaxfehler ergibt, weil Python bei einer Dezimalzahl keine führende ‚0‘ erlaubt (außer bei ‚0‘ selbst).
Wie wandle ich eine Zahl in eine Zeichenkette um?¶
Um zum Beispiel die Zahl 144 in die Zeichenkette '144' umzuwandeln, verwende den integrierten Typkonstruktor str(). Willst du eine hexadezimale oder oktale Darstellung, nimm die integrierten Funktionen hex() oder oct(). Für anspruchsvollere Formatierung siehe die Abschnitte f-Strings und Syntax für Formatzeichenketten. So ergibt "{:04d}".format(144) den Wert '0144' und "{:.3f}".format(1.0/3.0) den Wert '0.333'.
Wie ändere ich eine Zeichenkette an ihrem Platz?¶
Das geht nicht, weil Zeichenketten unveränderlich sind. In den meisten Fällen solltest du einfach eine neue Zeichenkette aus den Teilen zusammensetzen, aus denen du sie aufbauen willst. Brauchst du aber ein Objekt, mit dem sich Unicode-Daten an ihrem Platz ändern lassen, versuche ein io.StringIO-Objekt oder das Modul array:
>>> import io
>>> s = "Hello, world"
>>> sio = io.StringIO(s)
>>> sio.getvalue()
'Hello, world'
>>> sio.seek(7)
7
>>> sio.write("there!")
6
>>> sio.getvalue()
'Hello, there!'
>>> import array
>>> a = array.array('w', s)
>>> print(a)
array('w', 'Hello, world')
>>> a[0] = 'y'
>>> print(a)
array('w', 'yello, world')
>>> a.tounicode()
'yello, world'
Wie rufe ich über Zeichenketten Funktionen oder Methoden auf?¶
Dafür gibt es verschiedene Techniken.
Am besten ist ein Dictionary, das Zeichenketten auf Funktionen abbildet. Der wichtigste Vorteil dieser Technik ist, dass die Zeichenketten nicht mit den Namen der Funktionen übereinstimmen müssen. Sie ist außerdem die wichtigste Technik, um ein case-Konstrukt nachzubilden:
def a(): pass def b(): pass dispatch = {'go': a, 'stop': b} # Beachte die fehlenden Klammern bei den Funktionen dispatch[get_input()]() # Beachte die angehängten Klammern zum Aufruf der Funktion
Verwende die integrierte Funktion
getattr():import foo getattr(foo, 'bar')()
Beachte, dass
getattr()mit jedem Objekt funktioniert, auch mit Klassen, Klasseninstanzen, Modulen und so weiter.Das wird an mehreren Stellen in der Standardbibliothek so verwendet:
class Foo: def do_foo(self): ... def do_bar(self): ... f = getattr(foo_instance, 'do_' + opname) f()
Verwende
locals(), um den Funktionsnamen aufzulösen:def myFunc(): print("hello") fname = "myFunc" f = locals()[fname] f()
Gibt es eine Entsprechung zu chomp() aus Perl, um abschließende Zeilenumbrüche aus Zeichenketten zu entfernen?¶
Du kannst S.rstrip("\r\n") verwenden, um alle Vorkommen beliebiger Zeilenendezeichen vom Ende der Zeichenkette S zu entfernen, ohne andere abschließende Leerzeichen zu entfernen. Stellt die Zeichenkette S mehr als eine Zeile dar und endet mit mehreren Leerzeilen, werden die Zeilenendezeichen aller Leerzeilen entfernt:
>>> lines = ("line 1 \r\n"
... "\r\n"
... "\r\n")
>>> lines.rstrip("\n\r")
'line 1 '
Da man das typischerweise nur beim zeilenweisen Lesen von Text möchte, funktioniert S.rstrip() auf diese Weise gut.
Gibt es eine Entsprechung zu scanf() oder sscanf()?¶
Nicht als solche.
Für einfaches Einlesen ist es meist am leichtesten, die Zeile mit der Methode split() von Zeichenkettenobjekten in durch Leerzeichen getrennte Wörter zu zerlegen und die Dezimalzeichenketten dann mit int() oder float() in Zahlenwerte umzuwandeln. split() unterstützt einen optionalen Parameter „sep“, der nützlich ist, wenn die Zeile etwas anderes als Leerzeichen als Trennzeichen verwendet.
Für komplizierteres Einlesen sind reguläre Ausdrücke mächtiger als sscanf aus C und für die Aufgabe besser geeignet.
Was bedeutet der Fehler UnicodeDecodeError oder UnicodeEncodeError?¶
Siehe Unicode-HOWTO.
Darf eine rohe Zeichenkette mit einer ungeraden Anzahl von Rückwärtsschrägstrichen enden?¶
Eine rohe Zeichenkette, die mit einer ungeraden Anzahl von Rückwärtsschrägstrichen endet, maskiert das Anführungszeichen der Zeichenkette:
>>> r'C:\this\will\not\work\'
File "<stdin>", line 1
r'C:\this\will\not\work\'
^
SyntaxError: unterminated string literal (detected at line 1)
Dafür gibt es mehrere Auswege. Einer ist, gewöhnliche Zeichenketten zu verwenden und die Rückwärtsschrägstriche zu verdoppeln:
>>> 'C:\\this\\will\\work\\'
'C:\\this\\will\\work\\'
Ein anderer ist, eine gewöhnliche Zeichenkette mit einem maskierten Rückwärtsschrägstrich an die rohe Zeichenkette anzuhängen:
>>> r'C:\this\will\work' '\\'
'C:\\this\\will\\work\\'
Man kann unter Windows auch os.path.join() verwenden, um einen Rückwärtsschrägstrich anzuhängen:
>>> os.path.join(r'C:\this\will\work', '')
'C:\\this\\will\\work\\'
Beachte: Ein Rückwärtsschrägstrich „maskiert“ ein Anführungszeichen zwar, soweit es darum geht, wo die rohe Zeichenkette endet, bei der Auswertung ihres Wertes findet aber keine Maskierung statt. Der Rückwärtsschrägstrich bleibt also im Wert der rohen Zeichenkette enthalten:
>>> r'backslash\'preserved'
"backslash\\'preserved"
Siehe auch die Festlegung in der Sprachreferenz.
Geschwindigkeit¶
Mein Programm ist zu langsam. Wie mache ich es schneller?¶
Das ist im Allgemeinen eine schwierige Frage. Hier zunächst eine Liste von Dingen, an die du denken solltest, bevor du tiefer einsteigst:
Die Geschwindigkeitseigenschaften unterscheiden sich zwischen den Python-Implementierungen. Diese FAQ bezieht sich auf CPython.
Das Verhalten kann sich zwischen Betriebssystemen unterscheiden, besonders bei Ein- und Ausgabe oder bei Multithreading.
Du solltest die Brennpunkte in deinem Programm immer vor dem Versuch finden, Code zu optimieren (siehe das Modul
profile).Vergleichsskripte zu schreiben erlaubt dir, bei der Suche nach Verbesserungen schnell zu arbeiten (siehe das Modul
timeit).Es ist sehr empfehlenswert, eine gute Testabdeckung zu haben (durch Modultests oder eine andere Technik), bevor du möglicherweise Rückschritte einbaust, die sich in raffinierten Optimierungen verstecken.
Davon abgesehen gibt es viele Tricks, um Python-Code zu beschleunigen. Hier einige allgemeine Grundsätze, die auf dem Weg zu einer annehmbaren Geschwindigkeit sehr weit tragen:
Deine Algorithmen schneller zu machen oder zu schnelleren zu wechseln bringt oft viel mehr, als überall im Code Tricks zur Mikrooptimierung zu verstreuen.
Verwende die richtigen Datenstrukturen. Sieh dir die Dokumentation zu den Integrierte Typen und zum Modul
collectionsan.Wenn die Standardbibliothek eine Grundfunktion für etwas bereitstellt, ist diese wahrscheinlich schneller als jede Alternative, die du dir ausdenkst — garantiert ist das allerdings nicht. Für Grundfunktionen, die in C geschrieben sind, gilt das doppelt, etwa für integrierte Funktionen und manche Erweiterungstypen. Nimm zum Sortieren zum Beispiel in jedem Fall die integrierte Methode
list.sort()oder die zugehörige Funktionsorted()(und sieh dir Sortiertechniken für Beispiele mittlerer Schwierigkeit an).Abstraktionen neigen dazu, Umwege zu erzeugen und den Interpreter zu mehr Arbeit zu zwingen. Überwiegen die Ebenen des Umwegs die Menge der nützlichen Arbeit, wird dein Programm langsamer. Du solltest übermäßige Abstraktion vermeiden, besonders in Form winziger Funktionen oder Methoden, die oft auch der Lesbarkeit schaden.
Hast du die Grenze dessen erreicht, was reines Python erlaubt, gibt es Werkzeuge, die dich weiterbringen. So kann Cython eine leicht abgewandelte Fassung von Python-Code in eine C-Erweiterung übersetzen und lässt sich auf vielen verschiedenen Plattformen einsetzen. Cython kann die Kompilierung (und optionale Typannotationen) nutzen, um deinen Code deutlich schneller zu machen als im interpretierten Betrieb. Wenn du deinen C-Kenntnissen traust, kannst du auch selbst ein C-Erweiterungsmodul schreiben.
Siehe auch
Die Wiki-Seite zu performance tips.
Was ist der effizienteste Weg, viele Zeichenketten aneinanderzufügen?¶
str- und bytes-Objekte sind unveränderlich, deshalb ist es ineffizient, viele Zeichenketten aneinanderzufügen, denn jede Verkettung erzeugt ein neues Objekt. Im allgemeinen Fall wachsen die Laufzeitkosten quadratisch mit der Gesamtlänge der Zeichenkette. Weitere Informationen stehen unter Zeitkomplexität von Operationen auf eingebauten Typen.
Um viele str-Objekte anzusammeln, ist die empfohlene Schreibweise, sie in eine Liste zu legen und am Ende str.join() aufzurufen:
chunks = []
for s in my_strings:
chunks.append(s)
result = ''.join(chunks)
(Eine andere recht effiziente Schreibweise ist io.StringIO.)
Um viele bytes-Objekte anzusammeln, ist die empfohlene Schreibweise, ein bytearray-Objekt mit Verkettung an seinem Platz zu erweitern, also mit dem Operator +=:
result = bytearray()
for b in my_bytes_objects:
result += b
Sequenzen (Tupel und Listen)¶
Wie wandle ich zwischen Tupeln und Listen um?¶
Der Typkonstruktor tuple(seq) wandelt jede Sequenz — genauer gesagt jedes iterierbare Objekt — in ein Tupel mit denselben Elementen in derselben Reihenfolge um.
So ergibt tuple([1, 2, 3]) den Wert (1, 2, 3) und tuple('abc') den Wert ('a', 'b', 'c'). Ist das Argument ein Tupel, wird keine Kopie angelegt, sondern dasselbe Objekt zurückgegeben. tuple() aufzurufen ist deshalb günstig, wenn du nicht sicher bist, ob ein Objekt schon ein Tupel ist.
Der Typkonstruktor list(seq) wandelt jede Sequenz oder jedes iterierbare Objekt in eine Liste mit denselben Elementen in derselben Reihenfolge um. So ergibt list((1, 2, 3)) den Wert [1, 2, 3] und list('abc') den Wert ['a', 'b', 'c']. Ist das Argument eine Liste, wird eine Kopie angelegt, genau wie bei seq[:].
Was ist ein negativer Index?¶
Python-Sequenzen werden mit positiven und mit negativen Zahlen indiziert. Bei den positiven Zahlen ist 0 der erste Index, 1 der zweite und so weiter. Bei den negativen Indizes ist -1 der letzte Index, -2 der vorletzte und so weiter. Stell dir seq[-n] als dasselbe wie seq[len(seq)-n] vor.
Negative Indizes können sehr praktisch sein. So ist S[:-1] die ganze Zeichenkette außer ihrem letzten Zeichen, was nützlich ist, um den abschließenden Zeilenumbruch aus einer Zeichenkette zu entfernen.
Wie durchlaufe ich eine Sequenz in umgekehrter Reihenfolge?¶
Verwende die integrierte Funktion reversed():
for x in reversed(sequence):
... # mach etwas mit x ...
Das lässt deine ursprüngliche Sequenz unberührt, sondern baut eine neue Kopie in umgekehrter Reihenfolge, über die dann iteriert wird.
Wie entfernt man Duplikate aus einer Liste?¶
Im Python Cookbook steht eine lange Erörterung der vielen Wege dazu:
Wenn es dir nichts ausmacht, die Liste umzusortieren, sortiere sie und durchlaufe sie dann vom Ende her, wobei du Duplikate unterwegs löschst:
if mylist:
mylist.sort()
last = mylist[-1]
for i in range(len(mylist)-2, -1, -1):
if last == mylist[i]:
del mylist[i]
else:
last = mylist[i]
Lassen sich alle Elemente der Liste als Mengenschlüssel verwenden, sind sie also alle hashbar, ist das oft schneller:
mylist = list(set(mylist))
Das wandelt die Liste in eine Menge um, entfernt damit die Duplikate, und dann wieder zurück in eine Liste.
Wie entfernt man mehrere Elemente aus einer Liste?¶
Wie beim Entfernen von Duplikaten ist es eine Möglichkeit, ausdrücklich rückwärts zu iterieren und dabei eine Löschbedingung zu prüfen. Einfacher und schneller ist es aber, Slices zu ersetzen und dabei implizit oder ausdrücklich vorwärts zu iterieren. Hier drei Varianten:
mylist[:] = filter(keep_function, mylist)
mylist[:] = (x for x in mylist if keep_condition)
mylist[:] = [x for x in mylist if keep_condition]
Die List Comprehension ist vermutlich am schnellsten.
Wie erzeugt man in Python ein Array?¶
Verwende eine Liste:
["this", 1, "is", "an", "array"]
Listen entsprechen in ihrer Zeitkomplexität den Arrays von C oder Pascal. Der wichtigste Unterschied ist, dass eine Python-Liste Objekte vieler verschiedener Typen enthalten kann.
Das Modul array stellt außerdem Methoden bereit, um Arrays mit festen Typen und kompakter Darstellung zu erzeugen, deren Indizierung ist aber langsamer als bei Listen. Beachte auch, dass NumPy und andere Pakete von Dritten array-artige Strukturen mit verschiedenen Eigenschaften festlegen.
Um verkettete Listen im Stil von Lisp zu erhalten, kannst du cons-Zellen mit Tupeln nachbilden:
lisp_list = ("like", ("this", ("example", None) ) )
Ist Veränderlichkeit gewünscht, könntest du statt Tupeln Listen verwenden. Die Entsprechung des car aus Lisp ist hier lisp_list[0], die Entsprechung von cdr ist lisp_list[1]. Mach das nur, wenn du sicher bist, dass du es wirklich brauchst, denn es ist meist deutlich langsamer, als Python-Listen zu verwenden.
Wie erzeuge ich eine mehrdimensionale Liste?¶
Du hast vermutlich versucht, ein mehrdimensionales Array so zu erzeugen:
>>> A = [[None] * 2] * 3
Das sieht richtig aus, wenn du es ausgibst:
>>> A
[[None, None], [None, None], [None, None]]
Aber wenn du einen Wert zuweist, erscheint er an mehreren Stellen:
>>> A[0][0] = 5
>>> A
[[5, None], [5, None], [5, None]]
Der Grund ist, dass das Vervielfachen einer Liste mit * keine Kopien erzeugt, sondern nur Referenzen auf die vorhandenen Objekte. Das *3 erzeugt eine Liste mit 3 Referenzen auf dieselbe Liste der Länge zwei. Änderungen an einer Zeile zeigen sich in allen Zeilen, was ganz sicher nicht ist, was du willst.
Empfohlen wird, zuerst eine Liste der gewünschten Länge zu erzeugen und dann jedes Element mit einer neu erzeugten Liste zu füllen:
A = [None] * 3
for i in range(3):
A[i] = [None] * 2
Das erzeugt eine Liste mit 3 verschiedenen Listen der Länge zwei. Du kannst auch eine List Comprehension verwenden:
w, h = 2, 3
A = [[None] * w for i in range(h)]
Oder du verwendest eine Erweiterung, die einen Matrix-Datentyp bereitstellt. Die bekannteste ist NumPy.
Wie wende ich eine Methode oder Funktion auf eine Folge von Objekten an?¶
Um eine Methode oder Funktion aufzurufen und die Rückgabewerte in einer Liste anzusammeln, ist eine List Comprehension eine elegante Lösung:
result = [obj.method() for obj in mylist]
result = [function(obj) for obj in mylist]
Willst du die Methode oder Funktion nur ausführen, ohne die Rückgabewerte zu speichern, genügt eine einfache for-Schleife:
for obj in mylist:
obj.method()
for obj in mylist:
function(obj)
Warum löst a_tuple[i] += [‚item‘] eine Ausnahme aus, obwohl die Addition funktioniert?¶
Das kommt daher, dass erweiterte Zuweisungsoperatoren eben Zuweisungs-Operatoren sind, zusammen mit dem Unterschied zwischen veränderlichen und unveränderlichen Objekten in Python.
Diese Erörterung gilt allgemein, wenn erweiterte Zuweisungsoperatoren auf Elemente eines Tupels angewendet werden, die auf veränderliche Objekte verweisen. Wir nehmen als Beispiel aber eine list und +=.
Wenn du geschrieben hast:
>>> a_tuple = (1, 2)
>>> a_tuple[0] += 1
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
Der Grund für die Ausnahme sollte sofort klar sein: 1 wird zu dem Objekt addiert, auf das a_tuple[0] verweist, also zu 1, wodurch das Ergebnisobjekt 2 entsteht. Wenn wir aber versuchen, das Ergebnis der Berechnung, die 2, dem Element 0 des Tupels zuzuweisen, erhalten wir einen Fehler, weil wir nicht ändern können, worauf ein Element eines Tupels verweist.
Hinter den Kulissen tut diese erweiterte Zuweisungsanweisung ungefähr Folgendes:
>>> result = a_tuple[0] + 1
>>> a_tuple[0] = result
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
Es ist der Zuweisungsteil der Operation, der den Fehler erzeugt, denn ein Tupel ist unveränderlich.
Wenn du etwas wie dies schreibst:
>>> a_tuple = (['foo'], 'bar')
>>> a_tuple[0] += ['item']
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
ist die Ausnahme etwas überraschender, und noch überraschender ist, dass das Anhängen trotz des Fehlers funktioniert hat:
>>> a_tuple[0]
['foo', 'item']
Um zu verstehen, warum das passiert, musst du zwei Dinge wissen. Erstens: Setzt ein Objekt die magische Methode __iadd__() um, wird diese beim Ausführen der erweiterten Zuweisung += aufgerufen, und ihr Rückgabewert ist das, was in der Zuweisungsanweisung verwendet wird. Zweitens: Bei Listen entspricht __iadd__() dem Aufruf von extend() auf der Liste und der Rückgabe dieser Liste. Deshalb sagen wir, dass += bei Listen eine „Kurzform“ für list.extend() ist:
>>> a_list = []
>>> a_list += [1]
>>> a_list
[1]
Das entspricht:
>>> result = a_list.__iadd__([1])
>>> a_list = result
Das Objekt, auf das a_list verweist, wurde verändert, und der Verweis auf das veränderte Objekt wird a_list wieder zugewiesen. Das Endergebnis der Zuweisung ist ein Leerlauf, denn es ist ein Verweis auf dasselbe Objekt, auf das a_list vorher schon verwiesen hat — die Zuweisung findet aber dennoch statt.
In unserem Tupel-Beispiel passiert also etwas, das dem hier entspricht:
>>> result = a_tuple[0].__iadd__(['item'])
>>> a_tuple[0] = result
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
__iadd__() ist erfolgreich, die Liste wird also erweitert. Und obwohl result auf dasselbe Objekt verweist, auf das a_tuple[0] schon verweist, führt die abschließende Zuweisung trotzdem zu einem Fehler, weil Tupel unveränderlich sind.
Ich möchte kompliziert sortieren: Geht eine Schwartzian Transform in Python?¶
Die Technik, die Randal Schwartz aus der Perl-Gemeinschaft zugeschrieben wird, sortiert die Elemente einer Liste nach einem Maß, das jedes Element auf seinen „Sortierwert“ abbildet. Verwende in Python das Argument key der Methode list.sort():
Isorted = L[:]
Isorted.sort(key=lambda s: int(s[10:15]))
Wie sortiere ich eine Liste nach den Werten einer anderen Liste?¶
Füge sie zu einem Iterator von Tupeln zusammen, sortiere die sich ergebende Liste und greife dann das Element heraus, das du willst.
>>> list1 = ["what", "I'm", "sorting", "by"]
>>> list2 = ["something", "else", "to", "sort"]
>>> pairs = zip(list1, list2)
>>> pairs = sorted(pairs)
>>> pairs
[("I'm", 'else'), ('by', 'sort'), ('sorting', 'to'), ('what', 'something')]
>>> result = [x[1] for x in pairs]
>>> result
['else', 'sort', 'to', 'something']
Objekte¶
Was ist eine Klasse?¶
Eine Klasse ist der jeweilige Objekttyp, der beim Ausführen einer class-Anweisung erzeugt wird. Klassenobjekte dienen als Vorlagen, um Instanzobjekte zu erzeugen, die sowohl die Daten (Attribute) als auch den Code (Methoden) enthalten, die für einen Datentyp spezifisch sind.
Eine Klasse kann auf einer oder mehreren anderen Klassen aufbauen, ihren Basisklassen. Sie erbt dann deren Attribute und Methoden. Dadurch lässt sich ein Objektmodell durch Vererbung schrittweise verfeinern. Du könntest eine allgemeine Klasse Mailbox haben, die grundlegende Zugriffsmethoden für ein Postfach bereitstellt, und Unterklassen wie MboxMailbox, MaildirMailbox und OutlookMailbox, die verschiedene konkrete Postfachformate behandeln.
Was ist eine Methode?¶
Eine Methode ist eine Funktion auf einem Objekt x, die du normalerweise als x.name(arguments...) aufrufst. Methoden werden als Funktionen innerhalb der Klassendefinition festgelegt:
class C:
def meth(self, arg):
return arg * 2 + self.attribute
Was ist self?¶
Self ist lediglich ein üblicher Name für das erste Argument einer Methode. Eine Methode, die als meth(self, a, b, c) festgelegt ist, sollte als x.meth(a, b, c) für irgendeine Instanz x der Klasse aufgerufen werden, in der die Definition steht. Die aufgerufene Methode wird davon ausgehen, als meth(x, a, b, c) aufgerufen worden zu sein.
Siehe auch Warum muss ‚self‘ in Methodendefinitionen und -aufrufen ausdrücklich angegeben werden?.
Wie prüfe ich, ob ein Objekt eine Instanz einer gegebenen Klasse oder einer ihrer Unterklassen ist?¶
Verwende die integrierte Funktion isinstance(obj, cls). Du kannst prüfen, ob ein Objekt eine Instanz einer von mehreren Klassen ist, indem du statt einer einzelnen Klasse ein Tupel angibst, zum Beispiel isinstance(obj, (class1, class2, ...)). Du kannst auch prüfen, ob ein Objekt einer der integrierten Typen von Python ist, zum Beispiel mit isinstance(obj, str) oder isinstance(obj, (int, float, complex)).
Beachte, dass isinstance() auch auf virtuelle Vererbung von einer abstrakten Basisklasse prüft. Der Test gibt für eine registrierte Klasse deshalb True zurück, selbst wenn sie weder direkt noch indirekt von ihr geerbt hat. Um auf „echte Vererbung“ zu prüfen, durchsuche die Reihenfolge der Methodenauflösung (MRO) der Klasse:
from collections.abc import Mapping
class P:
pass
class C(P):
pass
Mapping.register(P)
>>> c = C()
>>> isinstance(c, C) # direkt
True
>>> isinstance(c, P) # indirekt
True
>>> isinstance(c, Mapping) # virtuell
True
# tatsächliche Vererbungskette
>>> type(c).__mro__
(<class 'C'>, <class 'P'>, <class 'object'>)
# Test auf echte Vererbung
>>> Mapping in type(c).__mro__
False
Beachte, dass die meisten Programme isinstance() auf selbst definierte Klassen nicht sehr häufig anwenden. Entwickelst du die Klassen selbst, ist es objektorientiert saubererer Stil, auf den Klassen Methoden festzulegen, die ein bestimmtes Verhalten kapseln, statt die Klasse des Objekts zu prüfen und je nach Klasse etwas anderes zu tun. Hast du zum Beispiel eine Funktion, die etwas tut:
def search(obj):
if isinstance(obj, Mailbox):
... # Code zum Durchsuchen eines Postfachs
elif isinstance(obj, Document):
... # Code zum Durchsuchen eines Dokuments
elif ...
Ein besserer Ansatz ist, auf allen Klassen eine Methode search() festzulegen und sie einfach aufzurufen:
class Mailbox:
def search(self):
... # Code zum Durchsuchen eines Postfachs
class Document:
def search(self):
... # Code zum Durchsuchen eines Dokuments
obj.search()
Was ist Delegation?¶
Delegation ist eine objektorientierte Technik, auch Entwurfsmuster genannt. Nimm an, du hast ein Objekt x und willst das Verhalten nur einer seiner Methoden ändern. Du kannst eine neue Klasse erzeugen, die eine neue Umsetzung der Methode bereitstellt, die du ändern willst, und alle anderen Methoden an die entsprechende Methode von x delegiert.
Python-Programmierer können Delegation leicht umsetzen. Die folgende Klasse setzt zum Beispiel eine Klasse um, die sich wie eine Datei verhält, aber alle geschriebenen Daten in Großbuchstaben umwandelt:
class UpperOut:
def __init__(self, outfile):
self._outfile = outfile
def write(self, s):
self._outfile.write(s.upper())
def __getattr__(self, name):
return getattr(self._outfile, name)
Hier definiert die Klasse UpperOut die Methode write() neu, um die Argumentzeichenkette in Großbuchstaben umzuwandeln, bevor die zugrunde liegende Methode self._outfile.write() aufgerufen wird. Alle anderen Methoden werden an das zugrunde liegende Objekt self._outfile delegiert. Die Delegation geschieht über die Methode __getattr__(). Weitere Informationen zur Steuerung des Attributzugriffs stehen in der Sprachreferenz.
Beachte, dass Delegation in allgemeineren Fällen heikler werden kann. Müssen Attribute nicht nur gelesen, sondern auch gesetzt werden, muss die Klasse auch eine Methode __setattr__() festlegen, und zwar sorgfältig. Die grundlegende Umsetzung von __setattr__() entspricht ungefähr dem Folgenden:
class X:
...
def __setattr__(self, name, value):
self.__dict__[name] = value
...
Viele Umsetzungen von __setattr__() rufen object.__setattr__() auf, um ein Attribut auf self zu setzen, ohne eine unendliche Rekursion auszulösen:
class X:
def __setattr__(self, name, value):
# Hier die eigene Logik ...
object.__setattr__(self, name, value)
Alternativ lassen sich Attribute auch setzen, indem man Einträge direkt in self.__dict__ einfügt.
Wie rufe ich aus einer abgeleiteten Klasse eine Methode auf, die in der Basisklasse festgelegt ist und die sie erweitert?¶
Verwende die integrierte Funktion super():
class Derived(Base):
def meth(self):
super().meth() # ruft Base.meth auf
Im Beispiel bestimmt super() automatisch die Instanz, von der aus es aufgerufen wurde (den Wert von self), sucht die Reihenfolge der Methodenauflösung (MRO) mit type(self).__mro__ und gibt die in der MRO auf Derived folgende Klasse zurück: Base.
Wie organisiere ich meinen Code so, dass sich die Basisklasse leichter ändern lässt?¶
Du könntest die Basisklasse einem Alias zuweisen und vom Alias ableiten. Dann musst du nur noch den Wert ändern, der dem Alias zugewiesen ist. Dieser Trick ist übrigens auch praktisch, wenn du dynamisch entscheiden willst, welche Basisklasse verwendet wird, etwa abhängig von der Verfügbarkeit von Ressourcen. Beispiel:
class Base:
...
BaseAlias = Base
class Derived(BaseAlias):
...
Wie erzeuge ich statische Klassendaten und statische Klassenmethoden?¶
Python unterstützt sowohl statische Daten als auch statische Methoden im Sinne von C++ oder Java.
Für statische Daten legst du einfach ein Klassenattribut fest. Um dem Attribut einen neuen Wert zuzuweisen, musst du in der Zuweisung ausdrücklich den Klassennamen verwenden:
class C:
count = 0 # Anzahl der Aufrufe von C.__init__
def __init__(self):
C.count = C.count + 1
def getcount(self):
return C.count # oder return self.count
c.count verweist für jedes c, für das isinstance(c, C) gilt, ebenfalls auf C.count — es sei denn, c selbst oder eine Klasse auf dem Suchpfad der Basisklassen von c.__class__ zurück zu C überschreibt es.
Achtung: Innerhalb einer Methode von C erzeugt eine Zuweisung wie self.count = 42 eine neue, davon unabhängige Instanzvariable namens „count“ im eigenen Dictionary von self. Beim Neubinden eines klassenstatischen Datennamens muss immer die Klasse angegeben werden, ob innerhalb einer Methode oder nicht:
C.count = 314
Statische Methoden sind möglich:
class C:
@staticmethod
def static(arg1, arg2, arg3):
# Kein Parameter 'self'!
...
Ein weit geradlinigerer Weg, die Wirkung einer statischen Methode zu erreichen, ist allerdings eine einfache Funktion auf Modulebene:
def getcount():
return C.count
Ist dein Code so aufgebaut, dass er eine Klasse (oder eine eng zusammenhängende Klassenhierarchie) pro Modul festlegt, erreicht das die gewünschte Kapselung.
Wie überlade ich in Python Konstruktoren oder Methoden?¶
Diese Antwort gilt tatsächlich für alle Methoden, die Frage kommt aber meist zuerst im Zusammenhang mit Konstruktoren auf.
In C++ würdest du schreiben:
class C {
C() { cout << "No arguments\n"; }
C(int i) { cout << "Argument is " << i << "\n"; }
}
In Python musst du einen einzigen Konstruktor schreiben, der mit Vorgabeargumenten alle Fälle abfängt. Zum Beispiel:
class C:
def __init__(self, i=None):
if i is None:
print("No arguments")
else:
print("Argument is", i)
Das ist nicht völlig gleichwertig, in der Praxis aber nah genug dran.
Du könntest es auch mit einer Argumentliste veränderlicher Länge versuchen, zum Beispiel:
def __init__(self, *args):
...
Derselbe Ansatz funktioniert für alle Methodendefinitionen.
Ich versuche __spam zu verwenden und bekomme einen Fehler über _SomeClassName__spam.¶
Variablennamen mit zwei führenden Unterstrichen werden „verstümmelt“, um einen einfachen, aber wirksamen Weg zu bieten, klassenprivate Variablen festzulegen. Jeder Bezeichner der Form __spam (mindestens zwei führende Unterstriche, höchstens ein abschließender) wird textuell durch _classname__spam ersetzt, wobei classname der aktuelle Klassenname ohne führende Unterstriche ist.
Innerhalb der Klasse lässt sich der Bezeichner unverändert verwenden, für den Zugriff von außerhalb der Klasse muss aber der verstümmelte Name benutzt werden:
class A:
def __one(self):
return 1
def two(self):
return 2 * self.__one()
class B(A):
def three(self):
return 3 * self._A__one()
four = 4 * A()._A__one()
Insbesondere garantiert das keine Privatheit, denn ein Benutzer von außen kann absichtlich weiterhin auf das private Attribut zugreifen. Viele Python-Programmierer verwenden private Variablennamen deshalb gar nicht erst.
Siehe auch
Die Festlegungen zur Verstümmelung privater Namen für Details und Sonderfälle.
Meine Klasse legt __del__ fest, aber es wird nicht aufgerufen, wenn ich das Objekt lösche.¶
Dafür gibt es mehrere mögliche Gründe.
Die del-Anweisung ruft nicht zwingend __del__() auf – sie verringert lediglich den Referenzzähler des Objekts, und erst wenn dieser null erreicht, wird __del__() aufgerufen.
Enthalten deine Datenstrukturen zirkuläre Verweise (zum Beispiel ein Baum, in dem jeder Kindknoten einen Verweis auf den Elternknoten und jeder Elternknoten eine Liste von Kindknoten hat), gehen die Referenzzähler nie auf null zurück. Python führt von Zeit zu Zeit einen Algorithmus aus, um solche Zyklen zu erkennen, aber die Speicherbereinigung läuft vielleicht erst einige Zeit nachdem der letzte Verweis auf deine Datenstruktur verschwunden ist. Deine Methode __del__() wird deshalb möglicherweise zu einem unpassenden und zufälligen Zeitpunkt aufgerufen. Das ist unpraktisch, wenn du ein Problem nachstellen willst. Schlimmer noch: Die Reihenfolge, in der die Methoden __del__() der Objekte ausgeführt werden, ist beliebig. Du kannst gc.collect() aufrufen, um eine Bereinigung zu erzwingen, aber es gibt krankhafte Fälle, in denen Objekte nie bereinigt werden.
Trotz der Zyklusbereinigung ist es weiterhin eine gute Idee, auf Objekten eine ausdrückliche Methode close() festzulegen, die aufgerufen wird, sobald du mit ihnen fertig bist. Die Methode close() kann dann Attribute entfernen, die auf Unterobjekte verweisen. Rufe __del__() nicht direkt auf – __del__() sollte close() aufrufen, und close() sollte dafür sorgen, dass es für dasselbe Objekt mehr als einmal aufgerufen werden kann.
Ein anderer Weg, zirkuläre Verweise zu vermeiden, ist das Modul weakref, mit dem du auf Objekte verweisen kannst, ohne ihren Referenzzähler zu erhöhen. Baumartige Datenstrukturen sollten zum Beispiel schwache Referenzen für ihre Verweise auf Elternteile und Geschwister verwenden, falls sie diese überhaupt brauchen.
Löst deine Methode __del__() schließlich eine Ausnahme aus, wird eine Warnmeldung auf sys.stderr ausgegeben.
Wie bekomme ich eine Liste aller Instanzen einer gegebenen Klasse?¶
Python verfolgt nicht alle Instanzen einer Klasse (oder eines integrierten Typs). Du kannst den Konstruktor der Klasse so programmieren, dass er alle Instanzen verfolgt, indem er eine Liste schwacher Referenzen auf jede Instanz führt.
Warum scheint das Ergebnis von id() nicht eindeutig zu sein?¶
Die integrierte Funktion id() gibt eine ganze Zahl zurück, die während der Lebensdauer des Objekts garantiert eindeutig ist. Da das in CPython die Speicheradresse des Objekts ist, kommt es häufig vor, dass nach dem Löschen eines Objekts aus dem Speicher das nächste neu erzeugte Objekt an derselben Stelle im Speicher angelegt wird. Dieses Beispiel zeigt das:
>>> id(1000)
13901272
>>> id(2000)
13901272
Die beiden Kennungen gehören zu verschiedenen Ganzzahlobjekten, die vor dem Aufruf von id() erzeugt und unmittelbar danach gelöscht werden. Um sicherzugehen, dass Objekte, deren Kennung du untersuchen willst, noch am Leben sind, erzeuge einen weiteren Verweis auf das Objekt:
>>> a = 1000; b = 2000
>>> id(a)
13901272
>>> id(b)
13891296
Wann kann ich mich auf Identitätstests mit dem Operator is verlassen?¶
Der Operator is prüft die Identität von Objekten. Der Test a is b entspricht id(a) == id(b).
Die wichtigste Eigenschaft eines Identitätstests ist, dass ein Objekt immer mit sich selbst identisch ist: a is a gibt stets True zurück. Identitätstests sind üblicherweise schneller als Gleichheitstests. Und anders als Gleichheitstests geben Identitätstests garantiert ein boolesches True oder False zurück.
Identitätstests können Gleichheitstests aber nur dann ersetzen, wenn die Identität der Objekte gesichert ist. Im Allgemeinen gibt es drei Umstände, unter denen Identität garantiert ist:
Zuweisungen erzeugen neue Namen, ändern aber nicht die Identität des Objekts. Nach der Zuweisung
new = oldist garantiert, dassnew is oldgilt.Ein Objekt in einen Container zu legen, der Objektreferenzen speichert, ändert die Identität des Objekts nicht. Nach der Listenzuweisung
s[0] = xist garantiert, dasss[0] is xgilt.Ist ein Objekt ein Singleton, bedeutet das, dass es nur eine Instanz dieses Objekts geben kann. Nach den Zuweisungen
a = Noneundb = Noneist garantiert, dassa is bgilt, weilNoneein Singleton ist.
Unter den meisten anderen Umständen sind Identitätstests nicht ratsam, und Gleichheitstests sind vorzuziehen. Insbesondere sollten Identitätstests nicht verwendet werden, um Konstanten wie int und str zu prüfen, die nicht garantiert Singletons sind:
>>> a = 10_000_000
>>> b = 5_000_000
>>> c = b + 5_000_000
>>> a is c
False
>>> a = 'Python'
>>> b = 'Py'
>>> c = b + 'thon'
>>> a is c
False
Genauso sind neue Instanzen veränderlicher Container nie identisch:
>>> a = []
>>> b = []
>>> a is b
False
Im Code der Standardbibliothek wirst du mehrere übliche Muster für den richtigen Einsatz von Identitätstests sehen:
Wie von PEP 8 empfohlen, ist ein Identitätstest der beste Weg, um auf
Nonezu prüfen. Das liest sich im Code wie gewöhnliches Englisch und vermeidet Verwechslungen mit anderen Objekten, deren boolescher Wert zu falsch auswertet.Optionale Argumente zu erkennen kann heikel sein, wenn
Noneein gültiger Eingabewert ist. In solchen Fällen kannst du ein Singleton-Wächterobjekt erzeugen, das garantiert von allen anderen Objekten verschieden ist. So lässt sich zum Beispiel eine Methode umsetzen, die sich wiedict.pop()verhält:_sentinel = sentinel('_sentinel') def pop(self, key, default=_sentinel): if key in self: value = self[key] del self[key] return value if default is _sentinel: raise KeyError(key) return default
Umsetzungen von Containern müssen Gleichheitstests manchmal um Identitätstests ergänzen. Das verhindert, dass der Code von Objekten wie
float('NaN')in die Irre geführt wird, die nicht gleich sich selbst sind.
Hier zum Beispiel die Umsetzung von collections.abc.Sequence.__contains__():
def __contains__(self, value):
for v in self:
if v is value or v == value:
return True
return False
Wie kann eine Unterklasse steuern, welche Daten in einer unveränderlichen Instanz gespeichert werden?¶
Überschreibe beim Ableiten von einem unveränderlichen Typ die Methode __new__() statt der Methode __init__(). Letztere läuft erst nach dem Erzeugen einer Instanz, und das ist zu spät, um Daten in einer unveränderlichen Instanz zu ändern.
Alle diese unveränderlichen Klassen haben eine andere Signatur als ihre Elternklasse:
import datetime as dt
class FirstOfMonthDate(dt.date):
"Always choose the first day of the month"
def __new__(cls, year, month, day):
return super().__new__(cls, year, month, 1)
class NamedInt(int):
"Allow text names for some numbers"
xlat = {'zero': 0, 'one': 1, 'ten': 10}
def __new__(cls, value):
value = cls.xlat.get(value, value)
return super().__new__(cls, value)
class TitleStr(str):
"Convert str to name suitable for a URL path"
def __new__(cls, s):
s = s.lower().replace(' ', '-')
s = ''.join([c for c in s if c.isalnum() or c == '-'])
return super().__new__(cls, s)
Die Klassen lassen sich so verwenden:
>>> FirstOfMonthDate(2012, 2, 14)
FirstOfMonthDate(2012, 2, 1)
>>> NamedInt('ten')
10
>>> NamedInt(20)
20
>>> TitleStr('Blog: Why Python Rocks')
'blog-why-python-rocks'
Wie speichere ich Methodenaufrufe zwischen?¶
Die beiden wichtigsten Werkzeuge zum Zwischenspeichern von Methoden sind @functools.cached_property und @functools.lru_cache. Das erste speichert Ergebnisse auf Ebene der Instanz, das zweite auf Ebene der Klasse.
Der Weg über cached_property funktioniert nur mit Methoden, die keine Argumente annehmen. Er erzeugt keinen Verweis auf die Instanz. Das zwischengespeicherte Ergebnis der Methode bleibt nur so lange erhalten, wie die Instanz lebt.
Der Vorteil ist, dass das zwischengespeicherte Ergebnis sofort freigegeben wird, sobald eine Instanz nicht mehr verwendet wird. Der Nachteil ist, dass sich mit den Instanzen auch die angesammelten Ergebnisse der Methode anhäufen. Sie können unbegrenzt wachsen.
Der Weg über lru_cache funktioniert mit Methoden, die hashbare Argumente haben. Er erzeugt einen Verweis auf die Instanz, sofern nicht besondere Mühe darauf verwendet wird, schwache Referenzen zu übergeben.
Der Vorteil des Algorithmus, der am längsten nicht benutzte Einträge verwirft, ist, dass der Zwischenspeicher durch das angegebene maxsize begrenzt ist. Der Nachteil ist, dass Instanzen am Leben gehalten werden, bis sie aus dem Zwischenspeicher herausaltern oder dieser geleert wird.
Dieses Beispiel zeigt die verschiedenen Techniken:
class Weather:
"Lookup weather information on a government website"
def __init__(self, station_id):
self._station_id = station_id
# The _station_id is private and immutable
def current_temperature(self):
"Latest hourly observation"
# Do not cache this because old results
# can be out of date.
@cached_property
def location(self):
"Return the longitude/latitude coordinates of the station"
# Result only depends on the station_id
@lru_cache(maxsize=20)
def historic_rainfall(self, date, units='mm'):
"Rainfall on a given date"
# Depends on the station_id, date, and units.
Das Beispiel oben setzt voraus, dass sich station_id nie ändert. Sind die betreffenden Instanzattribute veränderlich, lässt sich der Weg über cached_property nicht zum Funktionieren bringen, weil er Änderungen an den Attributen nicht erkennen kann.
Damit der Weg über lru_cache funktioniert, wenn station_id veränderlich ist, muss die Klasse die Methoden __eq__() und __hash__() festlegen, damit der Zwischenspeicher betreffende Änderungen an Attributen erkennen kann:
class Weather:
"Example with a mutable station identifier"
def __init__(self, station_id):
self.station_id = station_id
def change_station(self, station_id):
self.station_id = station_id
def __eq__(self, other):
return self.station_id == other.station_id
def __hash__(self):
return hash(self.station_id)
@lru_cache(maxsize=20)
def historic_rainfall(self, date, units='cm'):
'Rainfall on a given date'
# Depends on the station_id, date, and units.
Module¶
Wie erzeuge ich eine .pyc-Datei?¶
Wird ein Modul zum ersten Mal importiert (oder hat sich die Quelldatei seit dem Erzeugen der aktuellen kompilierten Datei geändert), sollte eine .pyc-Datei mit dem kompilierten Code in einem Unterverzeichnis __pycache__ des Verzeichnisses erzeugt werden, in dem die .py-Datei liegt. Der Dateiname der .pyc-Datei beginnt mit demselben Namen wie die .py-Datei und endet auf .pyc, mit einem Mittelteil, der von der jeweiligen python-Binärdatei abhängt, die sie erzeugt hat. (Details stehen in PEP 3147.)
Ein Grund dafür, dass keine .pyc-Datei erzeugt wird, kann ein Rechteproblem beim Verzeichnis der Quelldatei sein, sodass das Unterverzeichnis __pycache__ nicht angelegt werden kann. Das passiert zum Beispiel, wenn du als ein Benutzer entwickelst, aber als ein anderer ausführst, etwa beim Testen mit einem Webserver.
Sofern die Umgebungsvariable PYTHONDONTWRITEBYTECODE nicht gesetzt ist, wird eine .pyc-Datei automatisch erzeugt, wenn du ein Modul importierst und Python die Möglichkeit dazu hat (Rechte, freier Platz und so weiter), ein Unterverzeichnis __pycache__ anzulegen und das kompilierte Modul dorthin zu schreiben.
Python auf ein Skript der obersten Ebene anzuwenden gilt nicht als Import, und es wird keine .pyc-Datei erzeugt. Hast du zum Beispiel ein Modul foo.py auf oberster Ebene, das ein weiteres Modul xyz.py importiert, dann wird beim Ausführen von foo (durch Eingabe von python foo.py als Shell-Befehl) eine .pyc-Datei für xyz erzeugt, weil xyz importiert wird, aber keine für foo, da foo.py nicht importiert wird.
Wenn du eine .pyc-Datei für foo erzeugen musst – also eine .pyc-Datei für ein Modul, das nicht importiert wird – kannst du das mit den Modulen py_compile und compileall tun.
Das Modul py_compile kann jedes Modul von Hand kompilieren. Eine Möglichkeit ist, die Funktion compile() dieses Moduls interaktiv zu verwenden:
>>> import py_compile
>>> py_compile.compile('foo.py')
Das schreibt die .pyc-Datei in ein Unterverzeichnis __pycache__ am selben Ort wie foo.py — oder du gibst mit dem optionalen Parameter cfile etwas anderes vor.
Du kannst mit dem Modul compileall auch alle Dateien in einem oder mehreren Verzeichnissen automatisch kompilieren. Von der Shell aus geht das, indem du compileall.py ausführst und den Pfad eines Verzeichnisses mit den zu kompilierenden Python-Dateien angibst:
python -m compileall .
Wie finde ich den aktuellen Modulnamen heraus?¶
Ein Modul kann seinen eigenen Modulnamen herausfinden, indem es die vordefinierte globale Variable __name__ ansieht. Hat diese den Wert '__main__', läuft das Programm als Skript. Viele Module, die üblicherweise durch Import verwendet werden, stellen außerdem eine Befehlszeilenschnittstelle oder einen Selbsttest bereit und führen diesen Code erst nach einer Prüfung von __name__ aus:
def main():
print('Running test...')
...
if __name__ == '__main__':
main()
Wie kann ich Module haben, die sich gegenseitig importieren?¶
Nimm an, du hast die folgenden Module:
foo.py:
from bar import bar_var
foo_var = 1
bar.py:
from foo import foo_var
bar_var = 2
Das Problem ist, dass der Interpreter die folgenden Schritte ausführt:
main importiert
fooLeere globale Variablen für
foowerden erzeugtfoowird kompiliert und beginnt zu laufenfooimportiertbarLeere globale Variablen für
barwerden erzeugtbarwird kompiliert und beginnt zu laufenbarimportiertfoo(was ohne Wirkung bleibt, da es schon ein Modul namensfoogibt)Der Import-Mechanismus versucht,
foo_varaus den globalen Variablen vonfoozu lesen, umbar.foo_var = foo.foo_varzu setzen
Der letzte Schritt scheitert, weil Python foo noch nicht fertig interpretiert hat und das Wörterbuch der globalen Symbole für foo noch leer ist.
Dasselbe passiert, wenn du import foo verwendest und dann im globalen Code auf foo.foo_var zugreifen willst.
Für dieses Problem gibt es (mindestens) drei mögliche Auswege.
Guido van Rossum empfiehlt, auf jede Verwendung von from <module> import ... zu verzichten und allen Code in Funktionen zu stecken. Initialisierungen globaler Variablen und von Klassenvariablen sollten ausschließlich Konstanten oder integrierte Funktionen verwenden. Das bedeutet, dass alles aus einem importierten Modul als <module>.<name> angesprochen wird.
Jim Roskind schlägt vor, die Schritte in jedem Modul in der folgenden Reihenfolge auszuführen:
Exporte (globale Variablen, Funktionen und Klassen, die keine importierten Basisklassen brauchen)
import-Anweisungenaktiver Code (einschließlich globaler Variablen, die aus importierten Werten initialisiert werden).
Van Rossum mag diesen Ansatz nicht besonders, weil die Importe an einer ungewohnten Stelle auftauchen, aber er funktioniert.
Matthias Urlichs empfiehlt, deinen Code so umzubauen, dass der rekursive Import von vornherein nicht nötig ist.
Diese Lösungen schließen sich nicht gegenseitig aus.
__import__(‚x.y.z‘) gibt <module ‚x‘> zurück. Wie komme ich an z?¶
Verwende stattdessen besser die Hilfsfunktion import_module() aus importlib:
z = importlib.import_module('x.y.z')
Wenn ich ein importiertes Modul bearbeite und neu importiere, erscheinen die Änderungen nicht. Warum passiert das?¶
Aus Gründen der Effizienz und der Konsistenz liest Python die Moduldatei nur beim ersten Import eines Moduls. Täte es das nicht, würde in einem Programm aus vielen Modulen, die alle dasselbe Grundmodul importieren, dieses Grundmodul viele Male immer wieder geparst. Um das erneute Lesen eines geänderten Moduls zu erzwingen, mach dies:
import importlib
import modname
importlib.reload(modname)
Warnung: Diese Technik ist nicht zu 100 % narrensicher. Insbesondere werden Module mit Anweisungen wie:
from modname import some_objects
weiterhin mit der alten Fassung der importierten Objekte arbeiten. Enthält das Modul Klassendefinitionen, werden bestehende Klasseninstanzen nicht auf die neue Klassendefinition umgestellt. Das kann zu dem folgenden paradoxen Verhalten führen:
>>> import importlib
>>> import cls
>>> c = cls.C() # eine Instanz von C erzeugen
>>> importlib.reload(cls)
<module 'cls' from 'cls.py'>
>>> isinstance(c, cls.C) # isinstance ist falsch?!?
False
Die Natur des Problems wird deutlich, wenn du die „Identität“ der Klassenobjekte ausgibst:
>>> hex(id(c.__class__))
'0x7352a0'
>>> hex(id(cls.C))
'0x4198d0'