FAQ zur Programmierung

Allgemeine Fragen

Gibt es einen Debugger auf Quellcode-Ebene mit Breakpoints, Einzelschrittverfahren etc.?

Ja.

Es werden mehrere Debugger für Python unten beschrieben, und die eingebaute Funktion breakpoint() erlaubt Ihnen, in jeden von ihnen einzutreten.

Das `pdb`-Modul ist ein einfacher, aber ausreichender Debugger im Konsolenmodus für Python. Es ist Teil der Standardbibliothek von Python und ist in der Library Reference Manual dokumentiert. Sie können auch Ihren eigenen Debugger schreiben, indem Sie den Code von `pdb` als Beispiel verwenden.

Die integrierte Entwicklungsumgebung (IDE) IDLE, die Teil der Standard-Python-Distribution ist (normalerweise verfügbar als Tools/scripts/idle3), enthält einen grafischen Debugger.

PythonWin ist eine Python-IDE, die einen GUI-Debugger auf Basis von `pdb` enthält. Der PythonWin-Debugger färbt Breakpoints farblich ein und bietet viele coole Funktionen wie das Debuggen von Nicht-PythonWin-Programmen. PythonWin ist als Teil des pywin32-Projekts und als Teil der ActivePython-Distribution erhältlich.

Eric ist eine IDE, die auf PyQt und der Scintilla-Editing-Komponente basiert.

trepan3k ist ein gdb-ähnlicher Debugger.

Visual Studio Code ist eine IDE mit Debugging-Tools, die sich in Versionskontrollsoftware integriert.

Es gibt eine Reihe von kommerziellen Python-IDEs, die grafische Debugger enthalten. Dazu gehören

Gibt es Werkzeuge, die beim Finden von Fehlern oder bei der statischen Analyse helfen?

Ja.

Pylint und Pyflakes führen grundlegende Prüfungen durch, die Ihnen helfen, Fehler früher zu erkennen.

Statische Typenprüfer wie Mypy, Pyre und Pytype können Typ-Hinweise im Python-Quellcode überprüfen.

Wie erstelle ich eine eigenständige Binärdatei aus einem Python-Skript?

Sie benötigen nicht die Fähigkeit, Python in C-Code zu kompilieren, wenn alles, was Sie wollen, ein eigenständiges Programm ist, das Benutzer herunterladen und ausführen können, ohne die Python-Distribution zuerst installieren zu müssen. Es gibt eine Reihe von Werkzeugen, die den vom Programm benötigten Satz von Modulen ermitteln und diese Module mit einer Python-Binärdatei binden, um eine einzelne ausführbare Datei zu erzeugen.

Eine Möglichkeit ist die Verwendung des `freeze`-Tools, das sich im Python-Quellbaum unter Tools/freeze befindet. Es konvertiert Python-Bytecode in C-Arrays; mit einem C-Compiler können Sie alle Ihre Module in ein neues Programm einbetten, das dann mit den Standard-Python-Modulen verknüpft wird.

Es funktioniert, indem es Ihren Quellcode rekursiv auf Import-Anweisungen (in beiden Formen) scannt und nach den Modulen im Standard-Python-Pfad sowie im Quellverzeichnis (für eingebaute Module) sucht. Anschließend wandelt es den Bytecode für in Python geschriebene Module in C-Code um (Array-Initialisierer, die mit dem `marshal`-Modul in Code-Objekte umgewandelt werden können) und erstellt eine speziell angepasste Konfigurationsdatei, die nur die integrierten Module enthält, die tatsächlich im Programm verwendet werden. Anschließend kompiliert es den generierten C-Code und verknüpft ihn mit dem Rest des Python-Interpreters, um eine in sich geschlossene Binärdatei zu bilden, die genau wie Ihr Skript funktioniert.

Die folgenden Pakete können bei der Erstellung von Konsolen- und GUI-Ausführungsdateien helfen

Gibt es Codierungsstandards oder einen Styleguide für Python-Programme?

Ja. Der für Standardbibliotheksmodule erforderliche Codierungsstil ist als PEP 8 dokumentiert.

Kernsprache

Warum erhalte ich eine UnboundLocalError, obwohl die Variable einen Wert hat?

Es kann überraschend sein, die UnboundLocalError in zuvor funktionierendem Code zu erhalten, wenn dieser durch Hinzufügen einer Zuweisungsanweisung irgendwo im Körper einer Funktion modifiziert wird.

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 einer UnboundLocalError

>>> foo()
Traceback (most recent call last):
  ...
UnboundLocalError: local variable 'x' referenced before assignment

Das liegt daran, dass, wenn Sie einer Variablen in einem Geltungsbereich eine Zuweisung vornehmen, diese Variable lokal für diesen Geltungsbereich wird und jede ähnlich benannte Variable im äußeren Geltungsbereich überschattet. Da die letzte Anweisung in `foo` `x` einen neuen Wert zuweist, erkennt der Compiler sie als lokale Variable. Folglich versucht der frühere `print(x)`, die nicht initialisierte lokale Variable auszugeben, und es kommt zu einem Fehler.

Im obigen Beispiel können Sie auf die Variable des äußeren Geltungsbereichs zugreifen, indem Sie sie als global deklarieren

>>> x = 10
>>> def foobar():
...     global x
...     print(x)
...     x += 1
...
>>> foobar()
10

Diese explizite Deklaration ist erforderlich, um Sie daran zu erinnern, dass Sie tatsächlich den Wert der Variablen im äußeren Geltungsbereich ändern (im Gegensatz zu der oberflächlich analogen Situation mit Klassen- und Instanzvariablen).

>>> print(x)
11

Ähnliches können Sie in einem verschachtelten Geltungsbereich mit dem Schlüsselwort nonlocal tun

>>> def foo():
...    x = 10
...    def bar():
...        nonlocal x
...        print(x)
...        x += 1
...    bar()
...    print(x)
...
>>> foo()
10
11

Was sind die Regeln für lokale und globale Variablen in Python?

In Python sind Variablen, auf die nur innerhalb einer Funktion verwiesen wird, implizit global. Wenn einer Variablen irgendwo im Körper der Funktion ein Wert zugewiesen wird, wird sie als lokal angenommen, es sei denn, sie wird explizit als global deklariert.

Obwohl zunächst etwas überraschend, erklärt ein Moment des Nachdenkens dies. Einerseits stellt die Anforderung von global für zugewiesene Variablen eine Barriere gegen unbeabsichtigte Nebeneffekte dar. Andererseits, wenn global für alle globalen Verweise erforderlich wäre, würden Sie ständig global verwenden. Sie müssten jede Referenz auf eine eingebaute Funktion oder eine Komponente eines importierten Moduls als global deklarieren. Diese Unübersichtlichkeit würde den Nutzen der global-Deklaration zur Identifizierung von Nebeneffekten zunichte machen.

Warum liefern Lambdas, die in einer Schleife mit unterschiedlichen Werten definiert wurden, alle das gleiche Ergebnis?

Angenommen, Sie verwenden eine `for`-Schleife, um ein paar verschiedene Lambdas (oder sogar normale Funktionen) zu definieren, z. B.

>>> squares = []
>>> for x in range(5):
...     squares.append(lambda: x**2)

Dies gibt Ihnen eine Liste, die 5 Lambdas enthält, die `x**2` berechnen. Sie würden erwarten, dass sie beim Aufruf jeweils 0, 1, 4, 9 und 16 zurückgeben. Wenn Sie es jedoch tatsächlich versuchen, werden Sie sehen, dass sie alle 16 zurückgeben.

>>> squares[2]()
16
>>> squares[4]()
16

Dies geschieht, weil `x` nicht lokal für die Lambdas ist, sondern im äußeren Geltungsbereich definiert ist und abgerufen wird, wenn das Lambda aufgerufen wird – nicht, wenn es definiert wird. Am Ende der Schleife ist der Wert von `x` 4, sodass alle Funktionen nun `4**2`, d. h. 16, zurückgeben. Sie können dies auch verifizieren, indem Sie den Wert von `x` ändern und sehen, wie sich die Ergebnisse der Lambdas ändern.

>>> x = 8
>>> squares[2]()
64

Um dies zu vermeiden, müssen Sie die Werte in Variablen speichern, die für die Lambdas lokal sind, sodass sie nicht vom Wert des globalen `x` abhängen.

>>> squares = []
>>> for x in range(5):
...     squares.append(lambda n=x: n**2)

Hier erstellt `n=x` eine neue Variable `n`, die lokal für das Lambda ist und beim Definieren des Lambdas berechnet wird, sodass sie denselben Wert hat, den `x` zu diesem Zeitpunkt in der Schleife hatte. Das bedeutet, dass der Wert von `n` im ersten Lambda 0, im zweiten 1, im dritten 2 usw. sein wird. Daher wird jedes Lambda nun das korrekte Ergebnis zurückgeben.

>>> squares[2]()
4
>>> squares[4]()
16

Beachten Sie, dass dieses Verhalten nicht nur für Lambdas gilt, sondern auch für reguläre Funktionen.

Wie teile ich globale Variablen über Module hinweg?

Der kanonische Weg, Informationen über Module innerhalb eines einzigen Programms zu teilen, ist die Erstellung eines speziellen Moduls (oft `config` oder `cfg` genannt). Importieren Sie einfach das `config`-Modul in allen Modulen Ihrer Anwendung; das Modul wird dann als globaler Name verfügbar. Da es nur eine Instanz jedes Moduls gibt, werden alle Änderungen am Modulobjekt überall reflektiert. Zum Beispiel

config.py

x = 0   # Default value of the 'x' configuration setting

mod.py

import config
config.x = 1

main.py

import config
import mod
print(config.x)

Beachten Sie, dass die Verwendung eines Moduls aus demselben Grund auch die Grundlage für die Implementierung des Singleton-Entwurfsmusters bildet.

Was sind die "Best Practices" für die Verwendung von import in einem Modul?

Verwenden Sie im Allgemeinen nicht from modulename import *. Dies überlädt den Namensraum des Importierenden und erschwert es Lintern erheblich, undefinierte Namen zu erkennen.

Importieren Sie Module am Anfang einer Datei. Dies macht deutlich, welche anderen Module Ihr Code benötigt, und vermeidet Fragen, ob der Modulname im Geltungsbereich liegt. Die Verwendung einer Importanweisung pro Zeile erleichtert das Hinzufügen und Löschen von Modulimporten, während die Verwendung mehrerer Importe pro Zeile weniger Bildschirmplatz benötigt.

Es ist eine gute Praxis, Module in der folgenden Reihenfolge zu importieren

  1. Module der Standardbibliothek – z. B. sys, os, argparse, re

  2. Module von Drittanbietern (alles, was im `site-packages`-Verzeichnis von Python installiert ist) – z. B. dateutil, requests, PIL.Image

  3. lokal entwickelte Module

Es ist manchmal notwendig, Importe in eine Funktion oder Klasse zu verschieben, um Probleme mit zirkulären Importen zu vermeiden. Gordon McMillan sagt

Zirkuläre Importe sind in Ordnung, wenn beide Module die Form "import <modulename>" verwenden. Sie schlagen fehl, wenn das zweite Modul einen Namen aus dem ersten holen möchte ("from modulename import name") und der Import auf der obersten Ebene erfolgt. Das liegt daran, dass die Namen im ersten Modul noch nicht verfügbar sind, da das erste Modul gerade das zweite importiert.

In diesem Fall, wenn das zweite Modul nur in einer Funktion verwendet wird, kann der Import problemlos in diese Funktion verschoben werden. Bis zum Aufruf des Imports ist das erste Modul mit der Initialisierung fertig und das zweite Modul kann seinen Import durchführen.

Es kann auch notwendig sein, Importe aus der obersten Ebene des Codes zu verschieben, wenn einige der Module plattformspezifisch sind. In diesem Fall ist es möglicherweise nicht einmal möglich, alle Module auf der obersten Ebene der Datei zu importieren. In diesem Fall ist der Import der richtigen Module im entsprechenden plattformspezifischen Code eine gute Option.

Verschieben Sie Importe nur in einen lokalen Geltungsbereich, z. B. innerhalb einer Funktionsdefinition, wenn dies notwendig ist, um ein Problem wie zirkuläre Importe zu lösen, oder wenn Sie versuchen, die Initialisierungszeit eines Moduls zu reduzieren. Diese Technik ist besonders hilfreich, wenn viele der Importe je nach Ausführung des Programms unnötig sind. Möglicherweise möchten Sie Importe auch in eine Funktion verschieben, wenn die Module nur dort verwendet werden. Beachten Sie, dass das erste Laden eines Moduls aufgrund der einmaligen Initialisierung des Moduls kostspielig sein kann, aber das mehrfache Laden eines Moduls ist praktisch kostenlos und kostet nur ein paar Dictionary-Lookups. Selbst wenn der Modulname aus dem Geltungsbereich verschwunden ist, ist das Modul wahrscheinlich in sys.modules verfügbar.

Warum werden Standardwerte zwischen Objekten geteilt?

Diese Art von Fehler beißt oft Programmier-Neulinge. Betrachten Sie diese Funktion

def foo(mydict={}):  # Danger: shared reference to one dict for all calls
    ... compute something ...
    mydict[key] = value
    return mydict

Beim ersten Aufruf der Funktion enthält `mydict` ein einziges Element. Beim zweiten Mal enthält `mydict` zwei Elemente, da, wenn `foo()` beginnt auszuführen, `mydict` bereits ein Element enthält.

Es wird oft erwartet, dass ein Funktionsaufruf neue Objekte für Standardwerte erstellt. Das passiert nicht. Standardwerte werden genau einmal erstellt, wenn die Funktion definiert wird. Wenn dieses Objekt geändert wird, wie das Dictionary in diesem Beispiel, beziehen sich nachfolgende Aufrufe der Funktion auf dieses geänderte Objekt.

Per Definition sind unveränderliche Objekte wie Zahlen, Zeichenketten, Tupel und `None` vor Änderungen geschützt. Änderungen an veränderlichen Objekten wie Dictionaries, Listen und Klassendefinitionen können zu Verwirrung führen.

Aufgrund dieser Funktion ist es eine gute Programmierpraxis, keine veränderlichen Objekte als Standardwerte zu verwenden. Verwenden Sie stattdessen `None` als Standardwert und überprüfen Sie innerhalb der Funktion, ob der Parameter `None` ist, und erstellen Sie eine neue Liste/ein neues Dictionary/was auch immer, falls dies der Fall ist. Schreiben Sie zum Beispiel nicht

def foo(mydict={}):
    ...

sondern

def foo(mydict=None):
    if mydict is None:
        mydict = {}  # create a new dict for local namespace

Dieses Feature kann nützlich sein. Wenn Sie eine Funktion haben, deren Berechnung zeitaufwendig ist, ist eine gängige Technik, die Parameter und den resultierenden Wert jedes Aufrufs der Funktion zu cachen und den gecachten Wert zurückzugeben, wenn derselbe Wert erneut angefordert wird. Dies nennt man "Memoisation" und kann wie folgt implementiert werden

# Callers can only provide two parameters and optionally pass _cache by keyword
def expensive(arg1, arg2, *, _cache={}):
    if (arg1, arg2) in _cache:
        return _cache[(arg1, arg2)]

    # Calculate the value
    result = ... expensive computation ...
    _cache[(arg1, arg2)] = result           # Store result in the cache
    return result

Sie könnten eine globale Variable verwenden, die ein Dictionary enthält, anstelle des Standardwerts; das ist Geschmackssache.

Wie kann ich optionale oder Schlüsselwortparameter von einer Funktion an eine andere übergeben?

Sammeln Sie die Argumente mit den `*`- und `**`-Spezifizierern in der Parameterliste der Funktion; dies gibt Ihnen die Positionsargumente als Tupel und die Schlüsselwortargumente als Dictionary. Sie können diese Argumente dann beim Aufrufen einer anderen Funktion mit `*` und `**` übergeben.

def f(x, *args, **kwargs):
    ...
    kwargs['width'] = '14.3c'
    ...
    g(x, *args, **kwargs)

Was ist der Unterschied zwischen Argumenten und Parametern?

Parameter sind durch die Namen definiert, die in einer Funktionsdefinition erscheinen, während Argumente die Werte sind, die tatsächlich an eine Funktion übergeben werden, wenn sie aufgerufen wird. Parameter definieren, welche Art von Argumenten eine Funktion akzeptieren kann. Zum Beispiel, gegeben die Funktionsdefinition

def func(foo, bar=None, **kwargs):
    pass

`foo`, `bar` und `kwargs` sind Parameter von `func`. Wenn jedoch `func` aufgerufen wird, z. B.

func(42, bar=314, extra=somevar)

sind die Werte `42`, `314` und `somevar` Argumente.

Warum hat das Ändern der Liste 'y' auch die Liste 'x' verändert?

Wenn Sie Code wie diesen geschrieben haben

>>> x = []
>>> y = x
>>> y.append(10)
>>> y
[10]
>>> x
[10]

fragen Sie sich vielleicht, warum das Anhängen eines Elements an `y` auch `x` verändert hat.

Es gibt zwei Faktoren, die zu diesem Ergebnis führen

  1. Variablen sind einfach Namen, die sich auf Objekte beziehen. Wenn Sie `y = x` tun, wird keine Kopie der Liste erstellt – es wird eine neue Variable `y` erstellt, die sich auf dasselbe Objekt bezieht, auf das sich `x` bezieht. Das bedeutet, dass es nur ein Objekt (die Liste) gibt und sowohl `x` als auch `y` sich darauf beziehen.

  2. Listen sind veränderlich, was bedeutet, dass Sie ihren Inhalt ändern können.

Nach dem Aufruf von `append()` hat sich der Inhalt des veränderlichen Objekts von `[]` zu `[10]` geändert. Da sich beide Variablen auf dasselbe Objekt beziehen, greift jeder Name auf den geänderten Wert `[10]` zu.

Wenn wir stattdessen `x` ein unveränderliches Objekt zuweisen

>>> x = 5  # ints are immutable
>>> y = x
>>> x = x + 1  # 5 can't be mutated, we are creating a new object here
>>> x
6
>>> y
5

wir sehen, dass in diesem Fall x und y nicht mehr gleich sind. Das liegt daran, dass ganze Zahlen unveränderlich sind und wenn wir x = x + 1 ausführen, verändern wir nicht die Ganzzahl 5 durch Erhöhung ihres Wertes; stattdessen erstellen wir ein neues Objekt (die Ganzzahl 6) und weisen es x zu (d.h. wir ändern, auf welches Objekt x verweist). Nach dieser Zuweisung haben wir zwei Objekte (die Ganzzahlen 6 und 5) und zwei Variablen, die auf sie verweisen (x verweist nun auf 6, aber y verweist immer noch auf 5).

Einige Operationen (z.B. y.append(10) und y.sort()) verändern das Objekt, während oberflächlich ähnliche Operationen (z.B. y = y + [10] und sorted(y)) ein neues Objekt erstellen. Im Allgemeinen in Python (und in allen Fällen in der Standardbibliothek) gibt eine Methode, die ein Objekt verändert, None zurück, um zu vermeiden, dass die beiden Arten von Operationen verwechselt werden. Wenn Sie also fälschlicherweise y.sort() schreiben und denken, es würde Ihnen eine sortierte Kopie von y liefern, erhalten Sie stattdessen None, was wahrscheinlich dazu führt, dass Ihr Programm einen leicht diagnostizierbaren Fehler erzeugt.

Es gibt jedoch eine Klasse von Operationen, bei denen dieselbe Operation manchmal unterschiedliche Verhaltensweisen mit unterschiedlichen Typen aufweist: die erweiterten Zuweisungsoperatoren. Zum Beispiel verändert += Listen, aber keine Tupel oder ganzen Zahlen (a_list += [1, 2, 3] ist äquivalent zu a_list.extend([1, 2, 3]) und verändert a_list, während some_tuple += (1, 2, 3) und some_int += 1 neue Objekte erstellen).

Mit anderen Worten

  • Wenn wir ein veränderliches Objekt haben (list, dict, set, etc.), können wir bestimmte Operationen verwenden, um es zu verändern, und alle Variablen, die darauf verweisen, werden die Änderung sehen.

  • Wenn wir ein unveränderliches Objekt haben (str, int, tuple, etc.), sehen alle Variablen, die darauf verweisen, immer denselben Wert, aber Operationen, die diesen Wert in einen neuen Wert umwandeln, geben immer ein neues Objekt zurück.

Wenn Sie wissen möchten, ob zwei Variablen auf dasselbe Objekt verweisen oder nicht, können Sie den Operator is oder die eingebaute Funktion id() verwenden.

Wie schreibe ich eine Funktion mit Ausgabeparametern (Call-by-Reference)?

Denken Sie daran, dass Argumente in Python per Zuweisung übergeben werden. Da die Zuweisung nur Referenzen auf Objekte erstellt, gibt es keine Aliasse zwischen einem Argumentnamen im Aufrufer und im Aufgerufenen, und somit kein Call-by-Reference per se. Sie können den gewünschten Effekt auf verschiedene Weise erzielen.

  1. Durch Rückgabe eines Tupels der Ergebnisse

    >>> def func1(a, b):
    ...     a = 'new-value'        # a and b are local names
    ...     b = b + 1              # assigned to new objects
    ...     return a, b            # return new values
    ...
    >>> x, y = 'old-value', 99
    >>> func1(x, y)
    ('new-value', 100)
    

    Dies ist fast immer die klarste Lösung.

  2. Durch Verwendung von globalen Variablen. Dies ist nicht Thread-sicher und wird nicht empfohlen.

  3. Durch Übergabe eines veränderlichen (in-place veränderbaren) Objekts

    >>> def func2(a):
    ...     a[0] = 'new-value'     # 'a' references a mutable list
    ...     a[1] = a[1] + 1        # changes a shared object
    ...
    >>> args = ['old-value', 99]
    >>> func2(args)
    >>> args
    ['new-value', 100]
    
  4. Durch Übergabe eines Wörterbuchs, das verändert wird

    >>> def func3(args):
    ...     args['a'] = 'new-value'     # args is a mutable dictionary
    ...     args['b'] = args['b'] + 1   # change it in-place
    ...
    >>> args = {'a': 'old-value', 'b': 99}
    >>> func3(args)
    >>> args
    {'a': 'new-value', 'b': 100}
    
  5. Oder Bündeln von Werten in einer Klasseninstanz

    >>> class Namespace:
    ...     def __init__(self, /, **args):
    ...         for key, value in args.items():
    ...             setattr(self, key, value)
    ...
    >>> def func4(args):
    ...     args.a = 'new-value'        # args is a mutable Namespace
    ...     args.b = args.b + 1         # change object in-place
    ...
    >>> args = Namespace(a='old-value', b=99)
    >>> func4(args)
    >>> vars(args)
    {'a': 'new-value', 'b': 100}
    

    Es gibt fast nie einen guten Grund, das so kompliziert zu machen.

Ihre beste Wahl ist es, ein Tupel zurückzugeben, das die mehreren Ergebnisse enthält.

Wie schreibe ich eine höhere Funktion in Python?

Sie haben zwei Möglichkeiten: Sie können verschachtelte Gültigkeitsbereiche verwenden oder Sie können aufrufbare Objekte verwenden. Nehmen wir zum Beispiel an, Sie möchten linear(a,b) definieren, das eine Funktion f(x) zurückgibt, die den Wert a*x+b berechnet. Unter Verwendung von verschachtelten Gültigkeitsbereichen

def linear(a, b):
    def result(x):
        return a * x + b
    return result

Oder unter Verwendung eines aufrufbaren Objekts

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

taxes = linear(0.3, 2)

ergibt ein aufrufbares Objekt, bei dem taxes(10e6) == 0.3 * 10e6 + 2 ist.

Der Ansatz mit aufrufbaren Objekten hat den Nachteil, dass er etwas langsamer ist und etwas längeren Code ergibt. Beachten Sie jedoch, dass eine Sammlung von Aufrufbaren ihre Signatur durch Vererbung teilen kann

class exponential(linear):
    # __init__ inherited
    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 dieselbe Zählvariable teilen.

Wie kopiere ich ein Objekt in Python?

Versuchen Sie im Allgemeinen copy.copy() oder copy.deepcopy() für den allgemeinen Fall. Nicht alle Objekte können kopiert werden, aber die meisten schon.

Einige Objekte können einfacher kopiert werden. Wörterbücher haben eine copy()-Methode

newdict = olddict.copy()

Sequenzen können durch Slicing kopiert werden

new_l = l[:]

Wie kann ich die Methoden oder Attribute eines Objekts finden?

Für eine Instanz x einer benutzerdefinierten Klasse gibt dir(x) eine alphabetische Liste der Namen zurück, die die Instanzattribute und Methoden sowie die von ihrer Klasse definierten Attribute enthält.

Wie kann mein Code den Namen eines Objekts ermitteln?

Im Allgemeinen kann er das nicht, da Objekte keine echten Namen haben. Im Wesentlichen bindet eine Zuweisung immer einen Namen an einen Wert; dasselbe gilt für def und class Anweisungen, aber in diesem Fall ist der Wert etwas Aufrufbares. Betrachten Sie den folgenden Code

>>> class A:
...     pass
...
>>> B = A
>>> a = B()
>>> b = a
>>> print(b)
<__main__.A object at 0x16D07CC>
>>> print(a)
<__main__.A object at 0x16D07CC>

Man könnte argumentieren, dass die Klasse einen Namen hat: Obwohl sie an zwei Namen gebunden und unter dem Namen B aufgerufen wird, wird die erstellte Instanz immer noch als Instanz der Klasse A gemeldet. Es ist jedoch 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 für Ihren Code nicht notwendig sein, die "Namen" bestimmter Werte zu "kennen". Sofern Sie nicht bewusst introspektive Programme schreiben, ist dies normalerweise ein Hinweis darauf, dass eine Änderung des Ansatzes vorteilhaft sein könnte.

In comp.lang.python gab Fredrik Lundh einmal eine ausgezeichnete Analogie als Antwort auf diese Frage

Auf die gleiche Weise, wie Sie den Namen der Katze herausfinden, die Sie auf Ihrer Veranda gefunden haben: Die Katze (das Objekt) selbst kann Ihnen ihren Namen nicht sagen und es ist ihr auch egal – also ist der einzige Weg, herauszufinden, wie sie genannt wird, alle Ihre Nachbarn (Namensräume) zu fragen, ob es ihre Katze (Objekt) ist…

…und wundern Sie sich nicht, wenn Sie feststellen, dass sie unter vielen Namen bekannt ist oder gar keinem Namen!

Was ist mit der Präzedenz von Kommaoperatoren los?

Komma ist kein Operator in Python. Betrachten Sie diese Sitzung

>>> "a" in "b", "a"
(False, 'a')

Da das Komma kein Operator, sondern ein Trennzeichen zwischen Ausdrücken ist, wird das oben Gesagte so ausgewertet, als hätten Sie eingegeben

("a" in "b"), "a"

not

"a" in ("b", "a")

Dasselbe gilt für die verschiedenen Zuweisungsoperatoren (=, += usw.). Sie sind keine echten Operatoren, sondern syntaktische Trennzeichen in Zuweisungsanweisungen.

Gibt es ein Äquivalent zu C's "?: " Ternäroperator?

Ja, das gibt es. Die Syntax lautet wie folgt

[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 ein gängiges Idiom die Verwendung von logischen Operatoren

[expression] and [on_true] or [on_false]

Dieses Idiom ist jedoch unsicher, da es falsche Ergebnisse liefern kann, wenn *on_true* einen falschen booleschen Wert hat. Daher ist es immer besser, die Form ... if ... else ... zu verwenden.

Ist es möglich, verschleierte Einzeiler in Python zu schreiben?

Ja. Normalerweise geschieht dies durch Verschachtelung von lambda innerhalb von lambda. Siehe die folgenden drei Beispiele, leicht angepasst von 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

Versuchen Sie das nicht zu Hause, Kinder!

Was bedeutet der Schrägstrich (/) in der Parameterliste einer Funktion?

Ein Schrägstrich in der Argumentliste einer Funktion bedeutet, dass die Parameter davor rein positionsabhängig sind. Rein positionsabhängige Parameter sind diejenigen ohne einen extern nutzbaren Namen. Beim Aufruf einer Funktion, die rein positionsabhängige Parameter akzeptiert, werden Argumente ausschließlich anhand ihrer Position den Parametern zugeordnet. Beispielsweise ist divmod() eine Funktion, die rein positionsabhängige Parameter akzeptiert. Ihre Dokumentation sieht wie folgt 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 positionsabhängig sind. Somit würde der Aufruf von divmod() mit Schlüsselwortargumenten zu einem Fehler führen

>>> 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 oktale Ziffer anzugeben, stellen Sie dem oktalen Wert eine Null und dann ein kleines oder großes "o" voran. Geben Sie beispielsweise ein, um die Variable "a" auf den oktalen Wert "10" (8 dezimal) zu setzen

>>> a = 0o10
>>> a
8

Hexadezimal ist genauso einfach. Stellen Sie der hexadezimalen Zahl einfach eine Null und dann ein kleines oder großes "x" voran. Hexadezimale Ziffern können klein oder groß geschrieben werden. Zum Beispiel im Python-Interpreter

>>> a = 0xa5
>>> a
165
>>> b = 0XB2
>>> b
178

Warum gibt -22 // 10 -3 zurück?

Dies wird hauptsächlich durch den Wunsch angetrieben, dass i % j das gleiche Vorzeichen wie j hat. Wenn Sie das wollen und auch

i == (i // j) * j + (i % j)

dann muss die Ganzzahldivision den Boden zurückgeben. C verlangt ebenfalls, dass diese Identität gilt, und dann müssen Compiler, die i // j abschneiden, dafür sorgen, dass i % j das gleiche Vorzeichen wie i hat.

Es gibt wenige reale Anwendungsfälle für i % j wenn j negativ ist. Wenn j positiv ist, gibt es viele, und in praktisch allen davon ist es nützlicher, wenn i % j größer oder gleich 0 ist. Wenn die Uhr jetzt 10 anzeigt, was hat sie vor 200 Stunden angezeigt? -190 % 12 == 2 ist nützlich; -190 % 12 == -10 ist ein Fehler, der darauf wartet, zuzuschlagen.

Wie erhalte ich ein Attribut eines Ganzzahl-Literals anstelle eines SyntaxError?

Der Versuch, ein Attribut eines Ganzzahl-Literals auf normale Weise nachzuschlagen, führt zu einem SyntaxError, da der Punkt als Dezimalpunkt interpretiert wird

>>> 1.__class__
  File "<stdin>", line 1
  1.__class__
   ^
SyntaxError: invalid decimal literal

Die Lösung besteht darin, das Literal vom Punkt durch ein Leerzeichen oder Klammern zu trennen.

>>> 1 .__class__
<class 'int'>
>>> (1).__class__
<class 'int'>

Wie konvertiere ich eine Zeichenkette in eine Zahl?

Für ganze Zahlen verwenden Sie den eingebauten Typkonstruktor int(), z.B. int('144') == 144. Ebenso konvertiert float() in eine Fließkommazahl, z.B. float('144') == 144.0.

Standardmäßig interpretieren diese die Zahl als Dezimalzahl, sodass int('0144') == 144 wahr ist und int('0x144') einen ValueError auslöst. int(string, base) nimmt die Basis, aus der konvertiert werden soll, als zweites optionales Argument, sodass int( '0x144', 16) == 324. Wenn die Basis als 0 angegeben wird, wird die Zahl gemäß den Python-Regeln interpretiert: ein führendes ‚0o‘ zeigt Oktal an und ‚0x‘ eine Hex-Zahl.

Verwenden Sie nicht die eingebaute Funktion eval(), wenn Sie nur Zeichenketten in Zahlen umwandeln müssen. eval() ist erheblich langsamer und stellt ein Sicherheitsrisiko dar: Jemand könnte Ihnen einen Python-Ausdruck übergeben, der unerwünschte Nebeneffekte haben könnte. Zum Beispiel könnte jemand __import__('os').system("rm -rf $HOME") übergeben, was Ihr Home-Verzeichnis löschen würde.

eval() hat auch die Auswirkung, Zahlen als Python-Ausdrücke zu interpretieren, so dass z.B. eval('09') einen Syntaxfehler ergibt, da Python keine führende ‚0‘ in einer Dezimalzahl erlaubt (außer ‚0‘).

Wie konvertiere ich eine Zahl in eine Zeichenkette?

Um z.B. die Zahl 144 in die Zeichenkette '144' umzuwandeln, verwenden Sie den eingebauten Typkonstruktor str(). Wenn Sie eine hexadezimale oder oktale Darstellung wünschen, verwenden Sie die eingebauten Funktionen hex() oder oct(). Für anspruchsvolle Formatierungen siehe die Abschnitte f-Strings und Format String Syntax, z.B. ergibt "{:04d}".format(144) '0144' und "{:.3f}".format(1.0/3.0) ergibt '0.333'.

Wie modifiziere ich eine Zeichenkette inplace?

Das geht nicht, da Zeichenketten unveränderlich sind. In den meisten Situationen sollten Sie einfach eine neue Zeichenkette aus den verschiedenen Teilen zusammensetzen, aus denen Sie sie bilden möchten. Wenn Sie jedoch ein Objekt benötigen, das Unicode-Daten inplace modifizieren kann, versuchen Sie, ein io.StringIO-Objekt oder das Modul array zu verwenden

>>> 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 verwende ich Zeichenketten, um Funktionen/Methoden aufzurufen?

Es gibt verschiedene Techniken.

  • Die beste Methode ist die Verwendung eines Wörterbuchs, das Zeichenketten auf Funktionen abbildet. Der Hauptvorteil dieser Technik ist, dass die Zeichenketten nicht mit den Namen der Funktionen übereinstimmen müssen. Dies ist auch die Haupttechnik, die zur Emulation einer Case-Anweisung verwendet wird

    def a():
        pass
    
    def b():
        pass
    
    dispatch = {'go': a, 'stop': b}  # Note lack of parens for funcs
    
    dispatch[get_input()]()  # Note trailing parens to call function
    
  • Verwenden Sie die eingebaute Funktion getattr()

    import foo
    getattr(foo, 'bar')()
    

    Beachten Sie, dass getattr() auf jedem Objekt funktioniert, einschließlich Klassen, Klasseninstanzen, Modulen usw.

    Dies wird an mehreren Stellen in der Standardbibliothek verwendet, wie hier

    class Foo:
        def do_foo(self):
            ...
    
        def do_bar(self):
            ...
    
    f = getattr(foo_instance, 'do_' + opname)
    f()
    
  • Verwenden Sie locals(), um den Funktionsnamen aufzulösen

    def myFunc():
        print("hello")
    
    fname = "myFunc"
    
    f = locals()[fname]
    f()
    

Gibt es ein Äquivalent zu Perl's chomp() zum Entfernen von nachgestellten Zeilenumbrüchen aus Zeichenketten?

Sie können S.rstrip("\r\n") verwenden, um alle Vorkommen von Zeilenabschlüssen vom Ende der Zeichenkette S zu entfernen, ohne andere nachgestellte Leerzeichen zu entfernen. Wenn die Zeichenkette S mehr als eine Zeile darstellt, mit mehreren leeren Zeilen am Ende, werden die Zeilenabschlüsse aller leeren Zeilen entfernt

>>> lines = ("line 1 \r\n"
...          "\r\n"
...          "\r\n")
>>> lines.rstrip("\n\r")
'line 1 '

Da dies normalerweise nur beim Lesen von Text zeilenweise gewünscht wird, funktioniert die Verwendung von S.rstrip() auf diese Weise gut.

Gibt es ein Äquivalent zu scanf() oder sscanf()?

Nicht direkt.

Für einfache Eingabeparse sind die einfachste Methode normalerweise, die Zeile in Leerzeichen-getrennte Wörter mithilfe der split()-Methode von Zeichenkettenobjekten aufzuteilen und dann Dezimalzeichenketten mit int() oder float() in numerische Werte zu konvertieren. split() unterstützt einen optionalen "sep"-Parameter, der nützlich ist, wenn die Zeile etwas anderes als Leerzeichen als Trennzeichen verwendet.

Für komplexere Eingabeparse sind reguläre Ausdrücke mächtiger als C's sscanf und besser für die Aufgabe geeignet.

Was bedeutet der Fehler UnicodeDecodeError oder UnicodeEncodeError?

Siehe das Unicode HOWTO.

Kann ich einen Raw-String mit einer ungeraden Anzahl von Backslashes beenden?

Ein Raw-String, der mit einer ungeraden Anzahl von Backslashes endet, maskiert das Anführungszeichen des Strings

>>> 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 Workarounds. Eine besteht darin, normale Strings zu verwenden und die Backslashes zu verdoppeln

>>> 'C:\\this\\will\\work\\'
'C:\\this\\will\\work\\'

Eine weitere besteht darin, einen normalen String, der einen maskierten Backslash enthält, an den Raw-String anzuhängen

>>> r'C:\this\will\work' '\\'
'C:\\this\\will\\work\\'

Es ist auch möglich, os.path.join() zu verwenden, um unter Windows einen Backslash anzuhängen

>>> os.path.join(r'C:\this\will\work', '')
'C:\\this\\will\\work\\'

Beachten Sie, dass ein Backslash zwar ein Anführungszeichen zum Bestimmen des Endes des Raw-Strings "maskiert", aber bei der Interpretation des Wertes des Raw-Strings keine Maskierung stattfindet. Das heißt, der Backslash bleibt im Wert des Raw-Strings vorhanden

>>> r'backslash\'preserved'
"backslash\\'preserved"

Siehe auch die Spezifikation im Sprachreferenz.

Performance

Mein Programm ist zu langsam. Wie beschleunige ich es?

Das ist im Allgemeinen schwierig. Zuerst hier eine Liste von Dingen, an die Sie sich erinnern sollten, bevor Sie tiefer einsteigen

  • Leistungseigenschaften variieren je nach Python-Implementierung. Diese FAQ konzentriert sich auf CPython.

  • Das Verhalten kann je nach Betriebssystem variieren, insbesondere wenn es um I/O oder Multithreading geht.

  • Sie sollten immer die Hotspots in Ihrem Programm finden, *bevor* Sie versuchen, Code zu optimieren (siehe das Modul profile).

  • Benchmark-Skripte ermöglichen es Ihnen, schnell zu iterieren, wenn Sie nach Verbesserungen suchen (siehe das Modul timeit).

  • Es wird dringend empfohlen, eine gute Codeabdeckung (durch Unit-Tests oder jede andere Technik) zu haben, bevor Sie möglicherweise Regressionen einführen, die sich in ausgeklügelten Optimierungen verstecken.

Davon abgesehen gibt es viele Tricks, um Python-Code zu beschleunigen. Hier sind einige allgemeine Prinzipien, die Ihnen helfen, akzeptable Leistungsstufen zu erreichen

  • Schnellere Algorithmen zu erstellen (oder zu schnelleren zu wechseln) kann wesentlich größere Vorteile bringen, als zu versuchen, Micro-Optimierungs-Tricks überall in Ihrem Code zu verteilen.

  • Verwenden Sie die richtigen Datenstrukturen. Studieren Sie die Dokumentation für die Built-in Types und das Modul collections.

  • Wenn die Standardbibliothek ein natives Element zur Erledigung einer Aufgabe bereitstellt, ist es wahrscheinlich (wenn auch nicht garantiert), dass es schneller ist als jede alternative Lösung, die Sie sich ausdenken. Dies gilt umso mehr für in C geschriebene native Elemente wie Builtins und einige Erweiterungstypen. Stellen Sie zum Beispiel sicher, dass Sie entweder die eingebaute Methode list.sort() oder die zugehörige Funktion sorted() zum Sortieren verwenden (und siehe die Sortiertechniken für Beispiele für mäßig fortgeschrittene Verwendung).

  • Abstraktionen neigen dazu, Umwege zu schaffen und den Interpreter mehr Arbeit verrichten zu lassen. Wenn die Umwege die Menge der geleisteten nützlichen Arbeit überwiegen, wird Ihr Programm langsamer. Sie sollten übermäßige Abstraktion vermeiden, insbesondere in Form von winzigen Funktionen oder Methoden (die oft auch der Lesbarkeit abträglich sind).

Wenn Sie das Limit dessen erreicht haben, was reines Python zulässt, gibt es Werkzeuge, um weiter zu gehen. Zum Beispiel kann Cython eine leicht modifizierte Version von Python-Code in eine C-Erweiterung kompilieren und auf vielen verschiedenen Plattformen verwendet werden. Cython kann die Kompilierung (und optionale Typannotationen) nutzen, um Ihren Code deutlich schneller zu machen als bei Interpretation. Wenn Sie von Ihren C-Programmierkenntnissen überzeugt sind, können Sie auch ein C-Erweiterungsmodul selbst schreiben.

Siehe auch

Die Wiki-Seite, die Leistungstipps gewidmet ist.

Was ist der effizienteste Weg, um viele Strings zu verketten?

str und bytes Objekte sind unveränderlich, daher ist das Verketten vieler Strings ineffizient, da jede Verkettung ein neues Objekt erzeugt. Im allgemeinen Fall sind die Gesamtlaufzeitkosten quadratisch in der gesamten Stringlänge.

Um viele str-Objekte zu sammeln, ist das empfohlene Idiom, sie in eine Liste zu legen und am Ende str.join() aufzurufen.

chunks = []
for s in my_strings:
    chunks.append(s)
result = ''.join(chunks)

(ein weiteres einigermaßen effizientes Idiom ist die Verwendung von io.StringIO)

Um viele bytes-Objekte zu sammeln, ist das empfohlene Idiom, ein bytearray-Objekt mittels In-Place-Verkettung zu erweitern (der Operator +=).

result = bytearray()
for b in my_bytes_objects:
    result += b

Sequenzen (Tupel/Listen)

Wie konvertiere ich zwischen Tupeln und Listen?

Der Typkonstruktor tuple(seq) konvertiert jede Sequenz (eigentlich jedes iterierbare Objekt) in ein Tupel mit denselben Elementen in derselben Reihenfolge.

Zum Beispiel erzeugt tuple([1, 2, 3]) (1, 2, 3) und tuple('abc') erzeugt ('a', 'b', 'c'). Wenn das Argument ein Tupel ist, wird keine Kopie erstellt, sondern dasselbe Objekt zurückgegeben, sodass der Aufruf von tuple() günstig ist, wenn Sie nicht sicher sind, dass ein Objekt bereits ein Tupel ist.

Der Typkonstruktor list(seq) konvertiert jede Sequenz oder jedes iterierbare Objekt in eine Liste mit denselben Elementen in derselben Reihenfolge. Zum Beispiel erzeugt list((1, 2, 3)) [1, 2, 3] und list('abc') erzeugt ['a', 'b', 'c']. Wenn das Argument eine Liste ist, wird eine Kopie erstellt, genau wie bei seq[:].

Was ist ein negativer Index?

Python-Sequenzen werden mit positiven und negativen Zahlen indiziert. Bei positiven Zahlen ist 0 der erste Index, 1 der zweite Index usw. Bei negativen Indizes ist -1 der letzte Index und -2 der vorletzte (vorletzte) Index usw. Betrachten Sie seq[-n] als dasselbe wie seq[len(seq)-n].

Die Verwendung negativer Indizes kann sehr praktisch sein. Zum Beispiel ist S[:-1] der gesamte String außer seinem letzten Zeichen, was nützlich ist, um den nachgestellten Zeilenumbruch aus einem String zu entfernen.

Wie iteriere ich rückwärts über eine Sequenz?

Verwenden Sie die eingebaute Funktion reversed().

for x in reversed(sequence):
    ...  # do something with x ...

Dies berührt Ihre ursprüngliche Sequenz nicht, sondern erstellt eine neue Kopie mit umgekehrter Reihenfolge zum Iterieren.

Wie entferne ich Duplikate aus einer Liste?

Siehe das Python Cookbook für eine lange Diskussion vieler Möglichkeiten, dies zu tun.

Wenn es Ihnen nichts ausmacht, die Liste neu anzuordnen, sortieren Sie sie und scannen Sie dann vom Ende der Liste aus und löschen Sie Duplikate, während Sie vorgehen.

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]

Wenn alle Elemente der Liste als Set-Schlüssel verwendet werden können (d. h. sie sind alle hashbar), ist dies oft schneller.

mylist = list(set(mylist))

Dies konvertiert die Liste in ein Set, wodurch Duplikate entfernt werden, und dann zurück in eine Liste.

Wie entfernt man mehrere Elemente aus einer Liste?

Wie beim Entfernen von Duplikaten ist eine explizite Iteration in umgekehrter Reihenfolge mit einer Löschanweisung eine Möglichkeit. Es ist jedoch einfacher und schneller, Slice-Ersetzungen mit impliziter oder expliziter Vorwärtsiteration zu verwenden. Hier sind 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 Listenkomprehension ist möglicherweise die schnellste.

Wie erstelle ich ein Array in Python?

Verwenden Sie eine Liste.

["this", 1, "is", "an", "array"]

Listen sind in Bezug auf ihre Zeitkomplexität mit C- oder Pascal-Arrays vergleichbar; der Hauptunterschied besteht darin, dass eine Python-Liste Objekte verschiedener Typen enthalten kann.

Das Modul array bietet auch Methoden zur Erstellung von Arrays fester Typen mit kompakten Darstellungen, diese sind jedoch langsamer zu indizieren als Listen. Beachten Sie auch, dass NumPy und andere Drittanbieterpakete Array-ähnliche Strukturen mit verschiedenen Eigenschaften definieren.

Um Lisp-ähnliche verkettete Listen zu erhalten, können Sie *Cons-Zellen* mithilfe von Tupeln emulieren.

lisp_list = ("like",  ("this",  ("example", None) ) )

Wenn Veränderlichkeit erwünscht ist, könnten Sie Listen anstelle von Tupeln verwenden. Hier ist das Analogon zu einem Lisp-*car* lisp_list[0] und das Analogon zu *cdr* ist lisp_list[1]. Tun Sie dies nur, wenn Sie sicher sind, dass Sie es wirklich brauchen, da es normalerweise viel langsamer ist als die Verwendung von Python-Listen.

Wie erstelle ich eine mehrdimensionale Liste?

Sie haben wahrscheinlich versucht, ein mehrdimensionales Array auf diese Weise zu erstellen.

>>> A = [[None] * 2] * 3

Das sieht korrekt aus, wenn Sie es ausgeben.

>>> A
[[None, None], [None, None], [None, None]]

Aber wenn Sie einen Wert zuweisen, erscheint er an mehreren Stellen.

>>> A[0][0] = 5
>>> A
[[5, None], [5, None], [5, None]]

Der Grund ist, dass das Replizieren einer Liste mit * keine Kopien erstellt, sondern nur Referenzen auf die vorhandenen Objekte. Das *3 erstellt eine Liste, die 3 Referenzen auf dieselbe Liste der Länge zwei enthält. Änderungen an einer Zeile erscheinen in allen Zeilen, was fast sicher nicht das ist, was Sie wollen.

Der vorgeschlagene Ansatz ist, zuerst eine Liste der gewünschten Länge zu erstellen und dann jedes Element mit einer neu erstellten Liste zu füllen.

A = [None] * 3
for i in range(3):
    A[i] = [None] * 2

Dies erzeugt eine Liste, die 3 verschiedene Listen der Länge zwei enthält. Sie können auch eine Listenkomprehension verwenden.

w, h = 2, 3
A = [[None] * w for i in range(h)]

Oder Sie können eine Erweiterung verwenden, die einen Matrixdatentyp bereitstellt; NumPy ist der bekannteste.

Wie wende ich eine Methode oder Funktion auf eine Sequenz von Objekten an?

Um eine Methode oder Funktion aufzurufen und die Rückgabewerte in einer Liste zu sammeln, ist eine Listenkomprehension eine elegante Lösung.

result = [obj.method() for obj in mylist]

result = [function(obj) for obj in mylist]

Um die Methode oder Funktion nur auszuführen, ohne die Rückgabewerte zu speichern, reicht eine einfache for-Schleife aus.

for obj in mylist:
    obj.method()

for obj in mylist:
    function(obj)

Warum wirft a_tuple[i] += [‘item’] eine Ausnahme, wenn die Addition funktioniert?

Dies liegt an einer Kombination der Tatsache, dass erweiterte Zuweisungsoperatoren *Zuweisungsoperatoren* sind und dem Unterschied zwischen veränderlichen und unveränderlichen Objekten in Python.

Diese Diskussion gilt im Allgemeinen, wenn erweiterte Zuweisungsoperatoren auf Elemente eines Tupels angewendet werden, die auf veränderliche Objekte zeigen, aber wir verwenden eine list und += als unser Beispiel.

Wenn Sie geschrieben hätten.

>>> 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 zum Objekt a_tuple[0] hinzugefügt (1), wodurch das Ergebnisobjekt 2 entsteht. Aber wenn wir versuchen, das Ergebnis der Berechnung, 2, dem Element 0 des Tupels zuzuweisen, erhalten wir einen Fehler, da wir nicht ändern können, worauf ein Element eines Tupels zeigt.

Unter der Haube macht 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 verursacht, da ein Tupel unveränderlich ist.

Wenn Sie so etwas schreiben.

>>> a_tuple = (['foo'], 'bar')
>>> a_tuple[0] += ['item']
Traceback (most recent call last):
  ...
TypeError: 'tuple' object does not support item assignment

Die Ausnahme ist etwas überraschender, und noch überraschender ist die Tatsache, dass die Anhängung funktionierte, obwohl es einen Fehler gab.

>>> a_tuple[0]
['foo', 'item']

Um zu verstehen, warum dies geschieht, müssen Sie wissen, dass (a) wenn ein Objekt eine magische Methode __iadd__() implementiert, diese aufgerufen wird, wenn die erweiterte Zuweisung += ausgeführt wird, und ihr Rückgabewert für die Zuweisungsanweisung verwendet wird; und (b) für Listen ist __iadd__() gleichbedeutend mit dem Aufruf von extend() auf der Liste und der Rückgabe der Liste. Deshalb sagen wir, dass für Listen += eine "Abkürzung" für list.extend() ist.

>>> a_list = []
>>> a_list += [1]
>>> a_list
[1]

Dies ist äquivalent zu

>>> result = a_list.__iadd__([1])
>>> a_list = result

Das Objekt, auf das a_list zeigt, wurde verändert, und der Zeiger auf das veränderte Objekt wird wieder an a_list zugewiesen. Das Endergebnis der Zuweisung ist ein No-Op, da es ein Zeiger auf dasselbe Objekt ist, auf das a_list zuvor zeigte, aber die Zuweisung findet trotzdem statt.

Somit ist in unserem Tupel-Beispiel das, was geschieht, äquivalent zu.

>>> result = a_tuple[0].__iadd__(['item'])
>>> a_tuple[0] = result
Traceback (most recent call last):
  ...
TypeError: 'tuple' object does not support item assignment

Der Aufruf von __iadd__() ist erfolgreich, und daher wird die Liste erweitert, aber obwohl result auf dasselbe Objekt zeigt, auf das a_tuple[0] bereits zeigt, führt diese endgültige Zuweisung immer noch zu einem Fehler, da Tupel unveränderlich sind.

Ich möchte eine komplizierte Sortierung durchführen: Kann man in Python einen Schwartzschen Transform durchführen?

Die Technik, Randal Schwartz aus der Perl-Community zugeschrieben, sortiert die Elemente einer Liste nach einer Metrik, die jedes Element auf seinen "Sortierwert" abbildet. In Python verwenden Sie das Argument key für die Methode list.sort().

Isorted = L[:]
Isorted.sort(key=lambda s: int(s[10:15]))

Wie kann ich eine Liste anhand der Werte einer anderen Liste sortieren?

Führen Sie sie zu einem Iterator von Tupeln zusammen, sortieren Sie die resultierende Liste und wählen Sie dann das gewünschte Element aus.

>>> 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 spezielle Objekttyp, der durch die Ausführung einer Klassenerklärung erstellt wird. Klassenobjekte dienen als Vorlagen zur Erstellung von Instanzobjekten, die sowohl die Daten (Attribute) als auch den Code (Methoden) verkörpern, die für einen Datentyp spezifisch sind.

Eine Klasse kann auf einer oder mehreren anderen Klassen basieren, die als ihre Basisklasse(n) bezeichnet werden. Sie erbt dann die Attribute und Methoden ihrer Basisklassen. Dies ermöglicht die sukzessive Verfeinerung eines Objektmodells durch Vererbung. Sie könnten eine generische Klasse Mailbox haben, die grundlegende Zugriffsfunktionen für ein Postfach bereitstellt, und Unterklassen wie MboxMailbox, MaildirMailbox, OutlookMailbox, die verschiedene spezifische Postfachformate handhaben.

Was ist eine Methode?

Eine Methode ist eine Funktion für ein bestimmtes Objekt x, die Sie normalerweise als x.name(arguments...) aufrufen. Methoden werden als Funktionen innerhalb der Klassendefinition definiert.

class C:
    def meth(self, arg):
        return arg * 2 + self.attribute

Was ist self?

Self ist lediglich ein konventioneller Name für das erste Argument einer Methode. Eine als meth(self, a, b, c) definierte Methode sollte als x.meth(a, b, c) für eine Instanz x der Klasse aufgerufen werden, in der die Definition erfolgt; die aufgerufene Methode wird glauben, sie wurde als meth(x, a, b, c) aufgerufen.

Siehe auch Warum muss 'self' explizit in Methodendefinitionen und Aufrufen verwendet werden?.

Wie prüfe ich, ob ein Objekt eine Instanz einer bestimmten Klasse oder einer ihrer Unterklassen ist?

Verwenden Sie die eingebaute Funktion isinstance(obj, cls). Sie können prüfen, ob ein Objekt eine Instanz einer von mehreren Klassen ist, indem Sie ein Tupel anstelle einer einzelnen Klasse angeben, z.B. isinstance(obj, (class1, class2, ...)), und können auch prüfen, ob ein Objekt einer von Pythons integrierten Typen ist, z.B. isinstance(obj, str) oder isinstance(obj, (int, float, complex)).

Beachten Sie, dass isinstance() auch auf virtuelle Vererbung von einer abstrakten Basisklasse prüft. Daher gibt der Test True für eine registrierte Klasse zurück, auch wenn sie nicht direkt oder indirekt davon geerbt wurde. Um eine "echte Vererbung" zu testen, durchsuchen Sie die MRO der Klasse.

from collections.abc import Mapping

class P:
     pass

class C(P):
    pass

Mapping.register(P)
>>> c = C()
>>> isinstance(c, C)        # direct
True
>>> isinstance(c, P)        # indirect
True
>>> isinstance(c, Mapping)  # virtual
True

# Actual inheritance chain
>>> type(c).__mro__
(<class 'C'>, <class 'P'>, <class 'object'>)

# Test for "true inheritance"
>>> Mapping in type(c).__mro__
False

Beachten Sie, dass die meisten Programme isinstance() bei benutzerdefinierten Klassen nicht sehr oft verwenden. Wenn Sie die Klassen selbst entwickeln, ist ein besserer objektorientierter Stil, Methoden auf den Klassen zu definieren, die ein bestimmtes Verhalten kapseln, anstatt die Klasse des Objekts zu prüfen und basierend auf der Klasse etwas anderes zu tun. Zum Beispiel, wenn Sie eine Funktion haben, die etwas tut.

def search(obj):
    if isinstance(obj, Mailbox):
        ...  # code to search a mailbox
    elif isinstance(obj, Document):
        ...  # code to search a document
    elif ...

Ein besserer Ansatz ist, eine search()-Methode auf allen Klassen zu definieren und sie einfach aufzurufen.

class Mailbox:
    def search(self):
        ...  # code to search a mailbox

class Document:
    def search(self):
        ...  # code to search a document

obj.search()

Was ist Delegation?

Delegation ist eine objektorientierte Technik (auch Design Pattern genannt). Nehmen wir an, Sie haben ein Objekt x und möchten das Verhalten nur einer seiner Methoden ändern. Sie können eine neue Klasse erstellen, die eine neue Implementierung der Methode bereitstellt, an der Sie interessiert sind, und alle anderen Methoden an die entsprechende Methode von x delegiert.

Python-Programmierer können Delegation leicht implementieren. Zum Beispiel implementiert die folgende Klasse eine Klasse, 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 überschreibt die Klasse UpperOut die Methode write(), um den Argument-String 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 wird über die Methode __getattr__() erreicht; konsultieren Sie die Sprachreferenz für weitere Informationen zur Steuerung des Attributzugriffs.

Beachten Sie, dass Delegation in allgemeineren Fällen kniffliger werden kann. Wenn Attribute sowohl gesetzt als auch abgerufen werden müssen, muss die Klasse auch eine Methode __setattr__() definieren, und zwar sorgfältig. Die grundlegende Implementierung von __setattr__() ist ungefähr äquivalent zu Folgendem.

class X:
    ...
    def __setattr__(self, name, value):
        self.__dict__[name] = value
    ...

Viele Implementierungen von __setattr__() rufen object.__setattr__() auf, um ein Attribut auf self zu setzen, ohne eine Endlosschleife zu verursachen.

class X:
    def __setattr__(self, name, value):
        # Custom logic here...
        object.__setattr__(self, name, value)

Alternativ ist es möglich, Attribute zu setzen, indem Einträge direkt in self.__dict__ eingefügt werden.

Wie rufe ich eine in einer Basisklasse definierte Methode aus einer abgeleiteten Klasse auf, die sie erweitert?

Verwenden Sie die eingebaute Funktion super().

class Derived(Base):
    def meth(self):
        super().meth()  # calls Base.meth

Im Beispiel bestimmt super() automatisch die Instanz, von der es aufgerufen wurde (der self-Wert), sucht die Methodenauflösungsreihenfolge (MRO) mit type(self).__mro__ und gibt das nächste Element nach Derived in der MRO zurück: Base.

Wie kann ich meinen Code organisieren, um die Änderung der Basisklasse zu erleichtern?

Sie könnten die Basisklasse einem Alias zuweisen und vom Alias erben. Dann müssen Sie nur noch den Wert ändern, der dem Alias zugewiesen wird. Übrigens ist dieser Trick auch praktisch, wenn Sie dynamisch entscheiden möchten (z. B. abhängig von der Verfügbarkeit von Ressourcen), welche Basisklasse verwendet werden soll. Beispiel.

class Base:
    ...

BaseAlias = Base

class Derived(BaseAlias):
    ...

Wie erstelle ich statische Klassendaten und statische Klassenmethoden?

Sowohl statische Daten als auch statische Methoden (im Sinne von C++ oder Java) werden in Python unterstützt.

Für statische Daten definieren Sie einfach ein Klassenattribut. Um dem Attribut einen neuen Wert zuzuweisen, müssen Sie explizit den Klassennamen in der Zuweisung verwenden.

class C:
    count = 0   # number of times C.__init__ called

    def __init__(self):
        C.count = C.count + 1

    def getcount(self):
        return C.count  # or return self.count

c.count bezieht sich auch auf C.count für jedes c, für das isinstance(c, C) gilt, es sei denn, es wird von c selbst oder von einer Klasse im Suchpfad der Basisklasse von c.__class__ zurück zu C überschrieben.

Vorsicht: Innerhalb einer Methode von C erstellt eine Zuweisung wie self.count = 42 eine neue und nicht zusammenhängende Instanz namens "count" im eigenen Dictionary von self. Das Binden eines klassenstatischen Datennamens muss immer die Klasse angeben, ob innerhalb einer Methode oder nicht.

C.count = 314

Statische Methoden sind möglich.

class C:
    @staticmethod
    def static(arg1, arg2, arg3):
        # No 'self' parameter!
        ...

Ein weitaus einfacherer Weg, den Effekt einer statischen Methode zu erzielen, ist jedoch eine einfache Funktion auf Modulebene.

def getcount():
    return C.count

Wenn Ihr Code so strukturiert ist, dass pro Modul eine Klasse (oder eine eng verwandte Klassenhierarchie) definiert wird, bietet dies die gewünschte Kapselung.

Wie kann ich Konstruktoren (oder Methoden) in Python überladen?

Diese Antwort gilt eigentlich für alle Methoden, aber die Frage taucht meist zuerst im Kontext von Konstruktoren auf.

In C++ würden Sie schreiben.

class C {
    C() { cout << "No arguments\n"; }
    C(int i) { cout << "Argument is " << i << "\n"; }
}

In Python müssen Sie einen einzigen Konstruktor schreiben, der alle Fälle mit Standardargumenten behandelt. Zum Beispiel.

class C:
    def __init__(self, i=None):
        if i is None:
            print("No arguments")
        else:
            print("Argument is", i)

Dies ist nicht vollständig äquivalent, aber in der Praxis nah genug.

Sie könnten auch versuchen, eine variable Argumentliste zu verwenden, z. B.

def __init__(self, *args):
    ...

Der gleiche Ansatz funktioniert für alle Methodendefinitionen.

Ich versuche, __spam zu verwenden, und erhalte eine Fehlermeldung über _SomeClassName__spam.

Variablennamen mit doppelten führenden Unterstrichen werden "gemangelt", um eine einfache, aber effektive Methode zur Definition von Klassen-privaten Variablen bereitzustellen. Jeder Bezeichner der Form __spam (mindestens zwei führende Unterstriche, höchstens ein nachgestellter Unterstrich) wird textuell ersetzt durch _classname__spam, wobei classname der aktuelle Klassenname mit entfernten führenden Unterstrichen ist.

Der Bezeichner kann unverändert innerhalb der Klasse verwendet werden, aber um ihn außerhalb der Klasse darauf zuzugreifen, muss der gemangelte Name verwendet 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 dies keine Privatsphäre, da ein externer Benutzer immer noch absichtlich auf das private Attribut zugreifen kann; viele Python-Programmierer machen sich überhaupt nicht die Mühe, private Variablennamen zu verwenden.

Siehe auch

Die Spezifikationen für das Mangling privater Namen für Details und Sonderfälle.

Meine Klasse definiert __del__, aber sie wird nicht aufgerufen, wenn ich das Objekt lösche.

Dafür gibt es mehrere mögliche Gründe.

Die Anweisung del ruft nicht unbedingt __del__() auf – sie dekrementiert lediglich den Referenzzähler des Objekts, und wenn dieser Null erreicht, wird __del__() aufgerufen.

Wenn Ihre Datenstrukturen zyklische Verknüpfungen enthalten (z. B. ein Baum, bei dem jedes Kind eine Elternreferenz und jedes Elternteil eine Liste von Kindern hat), gehen die Referenzzähler nie auf Null zurück. Gelegentlich führt Python einen Algorithmus aus, um solche Zyklen zu erkennen. Der Garbage Collector läuft jedoch möglicherweise einige Zeit, nachdem die letzte Referenz auf Ihre Datenstruktur verschwunden ist, sodass Ihre __del__()-Methode zu einem ungünstigen und zufälligen Zeitpunkt aufgerufen werden kann. Das ist unpraktisch, wenn Sie versuchen, ein Problem zu reproduzieren. Schlimmer noch, die Reihenfolge, in der die __del__()-Methoden von Objekten ausgeführt werden, ist beliebig. Sie können gc.collect() aufrufen, um eine Sammlung zu erzwingen, aber es *gibt* pathologische Fälle, in denen Objekte niemals gesammelt werden.

Trotz des Zyklen-Sammlers ist es immer noch eine gute Idee, eine explizite close()-Methode für Objekte zu definieren, die aufgerufen werden soll, wenn Sie mit ihnen fertig sind. Die close()-Methode kann dann Attribute entfernen, die auf Unterobjekte verweisen. Rufen Sie __del__() nicht direkt auf – __del__() sollte close() aufrufen, und close() sollte sicherstellen, dass es mehr als einmal für dasselbe Objekt aufgerufen werden kann.

Eine weitere Möglichkeit, zyklische Referenzen zu vermeiden, ist die Verwendung des weakref-Moduls, das es Ihnen ermöglicht, auf Objekte zu verweisen, ohne deren Referenzzähler zu erhöhen. Baumdatenstrukturen sollten beispielsweise schwache Referenzen für ihre Eltern- und Geschwisterreferenzen verwenden (falls sie diese benötigen!).

Wenn Ihre __del__()-Methode eine Ausnahme auslöst, wird eine Warnmeldung an sys.stderr ausgegeben.

Wie bekomme ich eine Liste aller Instanzen einer bestimmten Klasse?

Python verfolgt keine Instanzen einer Klasse (oder eines eingebauten Typs). Sie können den Konstruktor der Klasse so programmieren, dass er alle Instanzen verfolgt, indem er eine Liste von schwachen Referenzen auf jede Instanz beibehält.

Warum scheint das Ergebnis von id() nicht eindeutig zu sein?

Die eingebaute Funktion id() gibt eine Ganzzahl zurück, die während der Lebensdauer des Objekts eindeutig garantiert ist. Da dies in CPython die Speicheradresse des Objekts ist, passiert es häufig, dass nach dem Löschen eines Objekts aus dem Speicher das nächste frisch erstellte Objekt an derselben Speicherposition zugewiesen wird. Dies wird in diesem Beispiel veranschaulicht.

>>> id(1000)
13901272
>>> id(2000)
13901272

Die beiden IDs gehören zu unterschiedlichen Integer-Objekten, die vor dem Aufruf von id() erstellt und unmittelbar danach gelöscht werden. Um sicherzustellen, dass Objekte, deren ID Sie untersuchen möchten, noch am Leben sind, erstellen Sie eine weitere Referenz auf das Objekt.

>>> a = 1000; b = 2000
>>> id(a)
13901272
>>> id(b)
13891296

Wann kann ich mich auf Identitätstests mit dem is-Operator verlassen?

Der is-Operator testet auf Objektidentität. Der Test a is b ist äquivalent zu id(a) == id(b).

Die wichtigste Eigenschaft eines Identitätstests ist, dass ein Objekt immer mit sich selbst identisch ist, a is a gibt immer True zurück. Identitätstests sind normalerweise schneller als Gleichheitstests. Und im Gegensatz zu Gleichheitstests wird bei Identitätstests garantiert ein boolescher Wert True oder False zurückgegeben.

Identitätstests können jedoch *nur* für Gleichheitstests verwendet werden, wenn die Objektidentität gewährleistet ist. Im Allgemeinen gibt es drei Umstände, unter denen die Identität garantiert ist.

  1. Zuweisungen erstellen neue Namen, ändern aber nicht die Objektidentität. Nach der Zuweisung new = old ist garantiert, dass new is old.

  2. Das Platzieren eines Objekts in einem Container, der Objektverweise speichert, ändert die Objektidentität nicht. Nach der Listen-Zuweisung s[0] = x ist garantiert, dass s[0] is x.

  3. Wenn ein Objekt ein Singleton ist, bedeutet dies, dass nur eine Instanz dieses Objekts existieren kann. Nach den Zuweisungen a = None und b = None ist garantiert, dass a is b, da None ein Singleton ist.

In den meisten anderen Fällen sind Identitätstests nicht ratsam und Gleichheitstests werden bevorzugt. Insbesondere sollten Identitätstests nicht verwendet werden, um Konstanten wie int und str zu überprüfen, die nicht garantiert Singletons sind.

>>> a = 1000
>>> b = 500
>>> c = b + 500
>>> a is c
False

>>> a = 'Python'
>>> b = 'Py'
>>> c = b + 'thon'
>>> a is c
False

Ebenso sind neue Instanzen von veränderlichen Containern niemals identisch.

>>> a = []
>>> b = []
>>> a is b
False

Im Code der Standardbibliothek sehen Sie mehrere gängige Muster für die korrekte Verwendung von Identitätstests.

  1. Wie von PEP 8 empfohlen, ist ein Identitätstest die bevorzugte Methode zur Überprüfung auf None. Dies liest sich im Code wie einfaches Englisch und vermeidet Verwechslungen mit anderen Objekten, die boolesche Werte haben können, die zu falsch ausgewertet werden.

  2. Das Erkennen optionaler Argumente kann schwierig sein, wenn None ein gültiger Eingabewert ist. In solchen Situationen können Sie ein Singleton-Sentinel-Objekt erstellen, das garantiert von anderen Objekten verschieden ist. Hier ist beispielsweise, wie eine Methode implementiert werden kann, die wie dict.pop() funktioniert.

    _sentinel = object()
    
    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
    
  3. Container-Implementierungen müssen manchmal Gleichheitstests mit Identitätstests ergänzen. Dies verhindert, dass der Code durch Objekte wie float('NaN') verwechselt wird, die nicht gleich sich selbst sind.

Hier ist beispielsweise die Implementierung 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?

Beim Ableiten eines unveränderlichen Typs überschreiben Sie die __new__()-Methode anstelle der __init__()-Methode. Letztere läuft erst *nachdem* eine Instanz erstellt wurde, was zu spät ist, um Daten in einer unveränderlichen Instanz zu ändern.

Alle diese unveränderlichen Klassen haben eine andere Signatur als ihre Elternklasse.

from datetime import date

class FirstOfMonthDate(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 können wie folgt verwendet werden.

>>> FirstOfMonthDate(2012, 2, 14)
FirstOfMonthDate(2012, 2, 1)
>>> NamedInt('ten')
10
>>> NamedInt(20)
20
>>> TitleStr('Blog: Why Python Rocks')
'blog-why-python-rocks'

Wie cache ich Methodenaufrufe?

Die beiden Hauptwerkzeuge zum Caching von Methoden sind functools.cached_property() und functools.lru_cache(). Die erstere speichert Ergebnisse auf Instanzebene und die letztere auf Klassenebene.

Der cached_property-Ansatz funktioniert nur mit Methoden, die keine Argumente haben. Er erstellt keine Referenz auf die Instanz. Das gecachte Methoden-Ergebnis wird nur so lange gespeichert, wie die Instanz lebt.

Der Vorteil ist, dass das gecachte Methoden-Ergebnis sofort freigegeben wird, wenn eine Instanz nicht mehr verwendet wird. Der Nachteil ist, dass sich mit dem Ansammeln von Instanzen auch die angesammelten Methoden-Ergebnisse anhäufen. Sie können unbegrenzt wachsen.

Der lru_cache-Ansatz funktioniert mit Methoden, die hashable Argumente haben. Er erstellt eine Referenz auf die Instanz, es sei denn, es werden spezielle Anstrengungen unternommen, schwache Referenzen zu übergeben.

Der Vorteil des Least-Recently-Used-Algorithmus ist, dass der Cache durch die angegebene maxsize begrenzt ist. Der Nachteil ist, dass Instanzen am Leben gehalten werden, bis sie aus dem Cache verdrängt werden oder bis der Cache gelöscht 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 obige Beispiel geht davon aus, dass die station_id sich niemals ändert. Wenn die relevanten Instanzattribute veränderlich sind, kann der cached_property-Ansatz nicht funktionieren, da er Änderungen an den Attributen nicht erkennen kann.

Damit der lru_cache-Ansatz funktioniert, wenn die station_id veränderlich ist, muss die Klasse die Methoden __eq__() und __hash__() definieren, damit der Cache relevante Attributaktualisierungen 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 erstelle ich eine .pyc-Datei?

Wenn ein Modul zum ersten Mal importiert wird (oder wenn die Quelldatei seit der Erstellung der aktuellen kompilierten Datei geändert wurde), sollte eine .pyc-Datei mit dem kompilierten Code in einem Unterverzeichnis __pycache__ im Verzeichnis mit der .py-Datei erstellt werden. Die .pyc-Datei hat einen Dateinamen, der mit demselben Namen wie die .py-Datei beginnt und mit .pyc endet, wobei die mittlere Komponente vom jeweiligen python-Binärprogramm abhängt, das sie erstellt hat. (Weitere Details finden Sie in PEP 3147.)

Ein Grund, warum keine .pyc-Datei erstellt wird, kann ein Berechtigungsproblem mit dem Verzeichnis sein, das die Quelldatei enthält, was bedeutet, dass das Unterverzeichnis __pycache__ nicht erstellt werden kann. Dies kann zum Beispiel passieren, wenn Sie sich als ein Benutzer entwickeln, aber als ein anderer ausführen, z. B. beim Testen mit einem Webserver.

Sofern die Umgebungsvariable PYTHONDONTWRITEBYTECODE nicht gesetzt ist, ist die Erstellung einer .pyc-Datei automatisch, wenn Sie ein Modul importieren und Python die Möglichkeit (Berechtigungen, freier Speicherplatz usw.) hat, ein Unterverzeichnis __pycache__ zu erstellen und das kompilierte Modul in dieses Unterverzeichnis zu schreiben.

Das Ausführen von Python auf einem Skript auf oberster Ebene wird nicht als Import betrachtet und es wird keine .pyc-Datei erstellt. Wenn Sie beispielsweise ein Modul auf oberster Ebene foo.py haben, das ein anderes Modul xyz.py importiert, und Sie dann foo ausführen (indem Sie python foo.py als Shell-Befehl eingeben), wird für xyz eine .pyc-Datei erstellt, da xyz importiert wird, aber keine .pyc-Datei für foo, da foo.py nicht importiert wird.

Wenn Sie eine .pyc-Datei für foo erstellen müssen – das heißt, eine .pyc-Datei für ein Modul erstellen, das nicht importiert wird –, können Sie dies mit den Modulen py_compile und compileall tun.

Das Modul py_compile kann jedes Modul manuell kompilieren. Eine Möglichkeit ist die interaktive Verwendung der Funktion compile() in diesem Modul.

>>> import py_compile
>>> py_compile.compile('foo.py')

Dadurch wird die .pyc-Datei in ein Unterverzeichnis __pycache__ am selben Speicherort wie foo.py geschrieben (oder Sie können dies mit dem optionalen Parameter cfile überschreiben).

Sie können auch automatisch alle Dateien in einem Verzeichnis oder in Verzeichnissen kompilieren, indem Sie das Modul compileall verwenden. Dies können Sie von der Shell-Eingabeaufforderung aus tun, indem Sie compileall.py ausführen und den Pfad eines Verzeichnisses mit zu kompilierenden Python-Dateien angeben.

python -m compileall .

Wie finde ich den Namen des aktuellen Moduls?

Ein Modul kann seinen eigenen Modulnamen herausfinden, indem es die vordefinierte globale Variable __name__ betrachtet. Wenn dieser den Wert '__main__' hat, wird das Programm als Skript ausgeführt. Viele Module, die üblicherweise durch Importieren verwendet werden, bieten auch eine Befehlszeilenschnittstelle oder Selbsttests und führen diesen Code nur aus, nachdem sie __name__ überprüft haben.

def main():
    print('Running test...')
    ...

if __name__ == '__main__':
    main()

Wie können Module einander gegenseitig importieren?

Angenommen, Sie haben 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 foo.

  • Leere globale Variablen für foo werden erstellt.

  • foo wird kompiliert und beginnt mit der Ausführung.

  • foo importiert bar.

  • Leere globale Variablen für bar werden erstellt.

  • bar wird kompiliert und beginnt mit der Ausführung.

  • bar importiert foo (was eine No-Op ist, da es bereits ein Modul namens foo gibt).

  • Der Importmechanismus versucht, foo_var aus den globalen Variablen von foo zu lesen, um bar.foo_var = foo.foo_var zu setzen.

Der letzte Schritt schlägt fehl, da Python noch nicht mit der Interpretation von foo fertig ist und das globale Symbolverzeichnis für foo noch leer ist.

Dasselbe passiert, wenn Sie import foo verwenden und dann versuchen, im globalen Code auf foo.foo_var zuzugreifen.

Es gibt (mindestens) drei mögliche Workarounds für dieses Problem.

Guido van Rossum empfiehlt, alle Verwendungen von from <module> import ... zu vermeiden und allen Code innerhalb von Funktionen zu platzieren. Initialisierungen von globalen Variablen und Klassenvariablen sollten nur Konstanten oder eingebaute Funktionen verwenden. Das bedeutet, dass alles aus einem importierten Modul als <module>.<name> referenziert wird.

Jim Roskind schlägt vor, die Schritte in jedem Modul in folgender Reihenfolge auszuführen:

  • Exporte (Globale, Funktionen und Klassen, die keine importierten Basisklassen benötigen)

  • import-Anweisungen

  • Aktiver Code (einschließlich globaler Variablen, die aus importierten Werten initialisiert werden).

Van Rossum mag diesen Ansatz nicht sehr, da die Importe an einer seltsamen Stelle erscheinen, aber er funktioniert.

Matthias Urlichs empfiehlt, Ihren Code so umzustrukturieren, dass der rekursive Import gar nicht erst notwendig ist.

Diese Lösungen schließen sich nicht gegenseitig aus.

__import__(‘x.y.z’) gibt <module ‘x’> zurück; wie bekomme ich z?

Erwägen Sie stattdessen die Verwendung der Hilfsfunktion import_module() aus importlib.

z = importlib.import_module('x.y.z')

Wenn ich ein importiertes Modul bearbeite und es neu importiere, werden die Änderungen nicht angezeigt. Warum passiert das?

Aus Gründen der Effizienz und Konsistenz liest Python die Moduldatei nur beim ersten Import eines Moduls. Wenn nicht, würde in einem Programm, das aus vielen Modulen besteht, wobei jedes das gleiche Basismodul importiert, das Basismodul mehrmals geparst und neu geparst werden. Um das erneute Lesen eines geänderten Moduls zu erzwingen, tun Sie Folgendes:

import importlib
import modname
importlib.reload(modname)

Warnung: Diese Technik ist nicht 100% narrensicher. Insbesondere Module, die Anweisungen wie diese enthalten:

from modname import some_objects

werden weiterhin mit der alten Version der importierten Objekte funktionieren. Wenn das Modul Klassendefinitionen enthält, werden vorhandene Klasseninstanzen *nicht* aktualisiert, um die neue Klassendefinition zu verwenden. Dies kann zu folgendem paradoxen Verhalten führen:

>>> import importlib
>>> import cls
>>> c = cls.C()                # Create an instance of C
>>> importlib.reload(cls)
<module 'cls' from 'cls.py'>
>>> isinstance(c, cls.C)       # isinstance is false?!?
False

Die Natur des Problems wird deutlich, wenn Sie die „Identität“ der Klassenobjekte ausgeben.

>>> hex(id(c.__class__))
'0x7352a0'
>>> hex(id(cls.C))
'0x4198d0'