In den
vorherigen Teilen haben wir uns mit Slices, dem Auspacken / Packen von Sammlungen und einigen Funktionen von Booleschen Operationen und Typen befasst.
In den
Kommentaren wurde die Möglichkeit erwähnt, Sammlungen mit einem Skalar zu multiplizieren:
a = [0] * 3 s = 'a' * 2 print(a, s)
Ein mehr oder weniger erfahrener Python-Entwickler weiß, dass ihm
beim Schreiben ein
Kopiermechanismus fehlt
a = [0] b = a b[0] = 1 print(a, b)
Was gibt dann den folgenden Code aus?
b = a * 2 b[0] = 2 print(a, b)
Python arbeitet in diesem Fall nach dem Prinzip der geringsten Überraschung: In der Variablen a haben wir eine Einheit, dh b könnte deklariert werden und wie
b = [1] * 2
Das Verhalten in diesem Fall ist das gleiche:
b = a * 2 b[0] = 2 print(a, b)
Aber nicht so einfach: Python kopiert den Inhalt der Liste so oft, wie Sie ihn multipliziert haben, und erstellt eine neue Liste. Und in den Listen werden Links zu Werten gespeichert, und wenn beim Kopieren von Verweisen auf unveränderliche Typen auf diese Weise alles in Ordnung ist, aber mit veränderlicher Wirkung der Effekt des Fehlens des Kopierens während des Schreibens herauskommt.
row = [0] * 2 matrix = [row] * 2 print(matrix)
Listen Sie Generatoren und Numpy auf, um Ihnen in diesem Fall zu helfen.
Listen können hinzugefügt und sogar inkrementiert werden, und jeder Iterator kann rechts sein:
a = [0] a += (1,) a += {2} a += "ab" a += {1: 2} print(a)
Frage mit einem Trick (für ein Interview): In Python werden Parameter als Referenz oder als Wert übergeben?
def inc(a): a += 1 return a a = 5 print(inc(a)) print(a)
Wie wir sehen, haben Änderungen am Wert im Funktionskörper den Wert des Objekts außerhalb nicht geändert. Daraus können wir schließen, dass die Parameter als Wert übergeben werden, und den folgenden Code schreiben:
def appended(a): a += [1] return a a = [5] print(appended(a))
In Sprachen wie C ++ sind Variablen im Stapel und im dynamischen Speicher gespeichert. Wenn die Funktion aufgerufen wird, legen wir alle Argumente auf den Stapel, wonach wir die Kontrolle an die Funktion übertragen. Sie kennt die Größen und Offsets der Variablen auf dem Stapel, damit sie sie richtig interpretieren kann.
Gleichzeitig haben wir zwei Möglichkeiten: Kopieren Sie den Speicher der Variablen auf den Stapel oder legen Sie einen Verweis auf das Objekt im dynamischen Speicher (oder auf höheren Ebenen des Stapels) ab.
Wenn Sie die Werte auf dem Funktionsstapel ändern, ändern sich natürlich auch die Werte im dynamischen Speicher nicht. Wenn Sie jedoch den Speicherbereich durch Referenz ändern, ändern wir den gemeinsam genutzten Speicher, sodass alle Verknüpfungen mit demselben Speicherbereich den neuen Wert „sehen“.
In Python haben sie einen ähnlichen Mechanismus aufgegeben. Die Ersetzung ist der Zuweisungsmechanismus des Variablennamens mit dem Objekt, beispielsweise beim Erstellen einer Variablen:
var = "john"
Der Interpreter erstellt das Objekt "john" und die Variable "name" und ordnet das Objekt dann dem angegebenen Namen zu.
Wenn eine Funktion aufgerufen wird, werden keine neuen Objekte erstellt. Stattdessen wird in ihrem Bereich ein Name erstellt, der einem vorhandenen Objekt zugeordnet ist.
Aber Python hat veränderbare und unveränderliche Typen. Die zweite enthält beispielsweise Zahlen: Während arithmetischer Operationen ändern sich vorhandene Objekte nicht, es wird jedoch ein neues Objekt erstellt, dem der vorhandene Name zugeordnet wird. Wenn danach dem alten Objekt kein einziger Name zugeordnet ist, wird dieser mithilfe des Linkzählmechanismus gelöscht.
Wenn der Name einer Variablen vom Variablentyp zugeordnet ist, ändert sich während der Operationen mit ihm der Speicher des Objekts, und dementsprechend sehen alle Namen, die diesem Speicherbereich zugeordnet sind, die Änderungen.
Sie können darüber in der
Dokumentation lesen, die
hier ausführlicher beschrieben wird.
Ein weiteres Beispiel:
a = [1, 2, 3, 4, 5] def rev(l): l.reverse() return l l = a print(a, l)
Was aber, wenn wir uns entschieden haben, die Variable außerhalb der Funktion zu ändern? In diesem Fall hilft uns der globale Modifikator:
def change(): global a a += 1 a = 5 change() print(a)
Hinweis: Tun Sie dies nicht (nein, im Ernst, verwenden Sie keine globalen Variablen in Ihren Programmen und insbesondere nicht in Ihren eigenen). Es ist besser, nur einige Werte aus der Funktion zurückzugeben:
def func(a, b): return a + 1, b + 1
In Python gibt es jedoch einen anderen Bereich und ein entsprechendes Schlüsselwort:
def private(value=None): def getter(): return value def setter(v): nonlocal value value = v return getter, setter vget, vset = private(42) print(vget())
In diesem Beispiel haben wir eine Variable erstellt, die nur über Methoden geändert werden kann (und deren Wert erhalten wird). Sie können einen ähnlichen Mechanismus in Klassen verwenden:
def private(value=None): def getter(): return value def setter(v): nonlocal value value = v return getter, setter class Person: def __init__(self, name): self.getid, self.setid = private(name) adam = Person("adam") print(adam.getid()) print(adam.setid("john")) print(adam.getid()) print(dir(adam))
Aber vielleicht wäre es besser, uns auf die
Eigenschaften oder die Definition von
__getattr__ ,
__setattr__ zu beschränken .
Sie können sogar
__delattr__ definieren.
Ein weiteres Merkmal von Python ist das Vorhandensein von zwei Methoden zum Abrufen des Attributs: __getattr__ und
__getattribute__ .
Was ist der Unterschied zwischen ihnen? Das erste wird nur aufgerufen, wenn das Attribut in der Klasse nicht gefunden wurde, und das zweite ist bedingungslos. Wenn beide in der Klasse deklariert sind, wird __getattr__ nur aufgerufen, wenn es explizit in __getattribute__ aufgerufen wird oder wenn __getattribute__ einen AttributeError ausgelöst hat.
class Person(): def __getattr__(self, item): print("__getattr__") if item == "name": return "john" raise AttributeError def __getattribute__(self, item): print("__getattribute__") raise AttributeError person = Person() print(person.name)
Und zum Schluss ein Beispiel dafür, wie Python Variablen und Bereiche frei manipuliert:
e = 42 try: 1 / 0 except Exception as e: pass print(e)
Dies ist übrigens vielleicht das einzige Beispiel, bei dem die zweite Python besser ist als die dritte, weil sie Folgendes ausgibt:
... print(e)