Was passiert, wenn Sie den Wert einer Variablen einer anderen Variablen in Python zuweisen?

80

Dies ist mein zweiter Tag, an dem ich Python lerne (ich kenne die Grundlagen von C ++ und etwas OOP), und ich habe einige leichte Verwirrung hinsichtlich der Variablen in Python.

So verstehe ich sie derzeit:

Python-Variablen sind Verweise (oder Zeiger?) Auf Objekte (die entweder veränderlich oder unveränderlich sind). Wenn wir so etwas haben num = 5, wird das unveränderliche Objekt 5irgendwo im Speicher erstellt, und das Name-Objekt-Referenzpaar numwird in einem bestimmten Namespace erstellt. Wenn wir haben a = num, wird nichts kopiert, aber jetzt beziehen sich beide Variablen auf dasselbe Objekt und werden ademselben Namespace hinzugefügt.

Hier verwirrt mich mein Buch " Automatisiere das langweilige Zeug mit Python" . Da es sich um ein Neuling-Buch handelt, werden keine Objekte, Namespaces usw. erwähnt, und es wird versucht, den folgenden Code zu erklären:

>>> spam = 42
>>> cheese = spam
>>> spam = 100
>>> spam
100
>>> cheese
42

Die Erklärung, die es bietet, ist genau die gleiche wie die eines C ++ - Buches, über das ich mich nicht freue, da es sich um Verweise / Zeiger auf Objekte handelt. In diesem Fall wird in der dritten Zeile, da Ganzzahlen unveränderlich spamsind, ein völlig neuer Zeiger / Verweis auf eine andere Stelle im Speicher zugewiesen, dh der Speicher, auf den er ursprünglich zeigte, wurde nicht geändert. Daher cheesebeziehen wir uns auf das ursprüngliche Objekt, auf das von verwiesen wird spam. Ist das die richtige Erklärung?

Ruslan Mushkaev
quelle
5
Sie gespeichert 42in spam, nun Sie gespeichert spamin Käse Mittel cheese = 42, Danach Sie ersetzt spamzu 100, Du hast nicht bearbeitet cheese, deshalb ist cheesenach wie vor ist 42.
15
Wie Jonas in seiner Antwort betont, ist Unveränderlichkeit hier ein roter Hering: Sie würden genau das gleiche Ergebnis erzielen, wenn Sie ein veränderliches Objekt wie eine Liste verwenden würden. Der Punkt ist , dass Sie neu zuweisen die Variable auf eine neue Referenz.
Daniel Roseman
7
Mögliches Duplikat der Python-Variablenreferenzzuweisung
underscore_d
27
@mini: Ich stelle mir Python-Variablen lieber als Labels als als Buckets vor. Sie haben das Etikett spamauf die Nummer 42 geklebt. Als nächstes haben Sie das Etikett cheeseauf das als gekennzeichnete Objekt geklebt spam( wohlgemerkt nicht auf das Etikett selbst). Dann haben Sie das spamEtikett abgezogen und auf die Nummer 100 gesetzt.
Tim Pederick

Antworten:

81

Als C ++ - Entwickler können Sie sich Python-Variablen als Zeiger vorstellen.

Wenn Sie also schreiben spam = 100, bedeutet dies, dass Sie "den Zeiger zuweisen", der zuvor auf das Objekt 42zeigte, um auf das Objekt zu zeigen 100.

Früher cheesewurde zugewiesen, auf dasselbe Objekt spamzu zeigen, auf das gezeigt wurde, was sich 42zu diesem Zeitpunkt befand. Da Sie nicht geändert haben cheese, zeigt es immer noch auf 42.

Unveränderlichkeit hat in diesem Fall nichts damit zu tun, da die Zeigerzuweisung nichts an dem Objekt ändert, auf das gezeigt wird.

Jonas Adler
quelle
2
das gleiche wie Objekte in js
Dushyant Bangal
7
Unveränderlichkeit ist wichtig, da Sie die Referenz so sicher behandeln können, als wäre es ein Wert. Es ist riskanter, veränderliche Objekte so zu behandeln, als wären sie Werte.
Plugwash
21

So wie ich das sehe, gibt es unterschiedliche Ansichten einer Sprache.

  • Die Perspektive des "Sprachrechtsanwalts".
  • Die Perspektive des "praktischen Programmierers".
  • die "Implementierer" -Perspektive.

Aus der Sicht des Sprachrechtsanwalts "zeigen" Python-Variablen immer auf ein Objekt. Im Gegensatz zu Java und C ++ hängt das Verhalten von == <=> = etc jedoch vom Laufzeittyp der Objekte ab, auf die die Variablen zeigen. Darüber hinaus wird in Python die Speicherverwaltung von der Sprache übernommen.

Aus der Perspektive eines praktischen Programmierers können wir die Tatsache, dass Ganzzahlen, Zeichenfolgen, Tupel usw. unveränderliche * Objekte und keine geraden Werte sind, als irrelevantes Detail behandeln. Die Ausnahme ist, dass beim Speichern großer Mengen numerischer Daten möglicherweise Typen verwendet werden sollen, die die Werte direkt speichern können (z. B. numpy-Arrays), anstatt Typen, die ein Array voller Verweise auf winzige Objekte erhalten.

Aus Sicht der Implementierer haben die meisten Sprachen eine Art Als-ob-Regel, sodass die Implementierung korrekt ist, wenn die angegebenen Verhaltensweisen korrekt sind, unabhängig davon, wie die Dinge tatsächlich unter der Haube ausgeführt werden.

Ja, Ihre Erklärung ist aus Sicht eines Sprachrechtsanwalts richtig. Ihr Buch ist aus Sicht eines praktischen Programmierers korrekt. Was eine Implementierung tatsächlich tut, hängt von der Implementierung ab. In cpython sind Ganzzahlen echte Objekte, obwohl Ganzzahlen mit kleinen Werten aus einem Cache-Pool entnommen und nicht neu erstellt werden. Ich bin mir nicht sicher, was die anderen Implementierungen (z. B. Pypy und Jython) tun.

* Beachten Sie hier die Unterscheidung zwischen veränderlichen und unveränderlichen Objekten. Bei einem veränderlichen Objekt müssen wir vorsichtig sein, es "wie einen Wert" zu behandeln, da ein anderer Code es mutieren könnte. Mit einem unveränderlichen Objekt haben wir keine derartigen Bedenken.

Plugwash
quelle
20

Es ist richtig, dass Sie mehr oder weniger Variablen als Zeiger verwenden können. Beispielcode würde jedoch sehr hilfreich sein, um zu erklären, wie dies tatsächlich funktioniert.

Erstens werden wir die idFunktion stark nutzen :

Gibt die "Identität" eines Objekts zurück. Dies ist eine Ganzzahl, die für dieses Objekt während seiner Lebensdauer garantiert eindeutig und konstant ist. Zwei Objekte mit nicht überlappenden Lebensdauern können denselben id () -Wert haben.

Es ist wahrscheinlich, dass dies unterschiedliche absolute Werte auf Ihrem Computer zurückgibt.

Betrachten Sie dieses Beispiel:

>>> foo = 'a string'
>>> id(foo) 
4565302640
>>> bar = 'a different string'
>>> id(bar)
4565321816
>>> bar = foo
>>> id(bar) == id(foo)
True
>>> id(bar)
4565302640

Sie können sehen, dass:

  • Die ursprünglichen foo / bar haben unterschiedliche IDs, da sie auf unterschiedliche Objekte verweisen
  • Wenn bar foo zugewiesen ist, sind ihre IDs jetzt identisch. Dies ähnelt beiden, die auf dieselbe Stelle im Speicher verweisen, die Sie beim Erstellen eines C ++ - Zeigers sehen

Wenn wir den Wert von foo ändern, wird er einer anderen ID zugewiesen:

>>> foo = 42
>>> id(foo)
4561661488
>>> foo = 'oh no'
>>> id(foo)
4565257832

Eine interessante Beobachtung ist auch, dass Ganzzahlen implizit diese Funktionalität bis zu 256 haben:

>>> a = 100
>>> b = 100
>>> c = 100
>>> id(a) == id(b) == id(c)
True

Ab 256 ist dies jedoch nicht mehr der Fall:

>>> a = 256
>>> b = 256
>>> id(a) == id(b)
True
>>> a = 257
>>> b = 257
>>> id(a) == id(b)
False

Wenn Sie jedoch zuweisen a, bbleibt die ID wie zuvor gezeigt unverändert:

>>> a = b
>>> id(a) == id(b)
True
Enderland
quelle
18

Python ist weder Referenzübergabe noch Wertübergabe. Python-Variablen sind keine Zeiger, sie sind keine Referenzen, sie sind keine Werte. Python-Variablen sind Namen .

Stellen Sie sich das als "Pass-by-Alias" vor, wenn Sie denselben Phrasentyp oder möglicherweise "Pass-by-Object" benötigen, da Sie dasselbe Objekt aus jeder Variablen mutieren können, die es angibt, wenn es veränderbar ist, aber neu zugewiesen wird Eine Variable (Alias) ändert nur diese eine Variable.

Wenn es hilft: C-Variablen sind Felder, in die Sie Werte schreiben. Python-Namen sind Tags, die Sie auf Werte setzen.

Der Name einer Python-Variablen ist ein Schlüssel im globalen (oder lokalen) Namespace, der praktisch ein Wörterbuch ist. Der zugrunde liegende Wert ist ein Objekt im Speicher. Die Zuweisung gibt diesem Objekt einen Namen. Die Zuweisung einer Variablen zu einer anderen Variablen bedeutet, dass beide Variablen Namen für dasselbe Objekt sind. Durch die Neuzuweisung einer Variablen wird geändert, welches Objekt von dieser Variablen benannt wird, ohne dass die andere Variable geändert wird. Sie haben das Tag verschoben, aber das vorherige Objekt oder andere Tags darauf nicht geändert.

Im zugrunde liegenden C-Code der CPython-Implementierung ist jedes Python-Objekt ein PyObject*, sodass Sie sich vorstellen können, dass es wie C funktioniert, wenn Sie nur Zeiger auf Daten hatten (keine Zeiger auf Zeiger, keine direkt übergebenen Werte).

Sie könnten sagen, dass Python ein Wert ist, bei dem die Werte Zeiger sind, oder Sie könnten sagen, dass Python ein Referenzwert ist, bei dem die Referenzen Kopien sind.

Vivian
quelle
1
Das Problem bei der Bezeichnung "Pass-by-Name" besteht darin, dass es bereits eine Parameterübergabekonvention namens "Call-by-Name" mit einer völlig anderen Bedeutung gibt. Beim Aufruf nach Namen wird der Parameterausdruck jedes Mal ausgewertet, wenn die Funktion den Parameter verwendet, und niemals ausgewertet, wenn die Funktion den Parameter nicht verwendet.
user2357112 unterstützt Monica
11

Wenn Sie spam = 100Python ausführen, erstellen Sie ein weiteres Objekt im Speicher, ändern Sie jedoch nicht das vorhandene. Sie haben also immer noch einen Zeiger cheeseauf 42 und spamauf 100

Oleksandr Dashkov
quelle
8

In der spam = 100Zeile wird der vorherige Wert (Zeiger auf ein Objekt vom Typ intdurch einen Wert 42) durch einen anderen Zeiger auf ein anderes Objekt (Typ int, Wert 100) ersetzt.

Bakatrouble
quelle
Ganzzahlen sind Wertobjekte, die sich auf dem Stapel befinden, oder?
Gert Kommer
Ja, es handelt sich um Objekte, die Sie mithilfe der new Class()Syntax in C ++ erstellen . Darüber hinaus ist in Python alles eine Instanz von objectKlasse / Unterklasse.
Bakatrouble
4
@GertKommer Zumindest in CPython leben alle Objekte auf dem Heap. Es gibt keine Unterscheidung zwischen einem "Wertobjekt". Es gibt nur Objekte und alles ist ein Objekt . Aus diesem Grund beträgt die Größe eines typischen int je nach Python-Version etwa 28 Byte, da es den gesamten Py_Object-Overhead hat. Small-Ints werden als CPython-Optimierung zwischengespeichert.
juanpa.arrivillaga
8

Wie @DeepSpace in den Kommentaren erwähnt hat, leistet Ned Batchelder hervorragende Arbeit bei der Entmystifizierung von Variablen (Namen) und Zuweisungen zu Werten in einem Blog, aus dem er auf der PyCon 2015 einen Vortrag über Fakten und Mythen zu Python-Namen und -Werten hielt . Es kann für Pythonisten auf jeder Ebene der Meisterschaft aufschlussreich sein.

Pylang
quelle
1

In Python enthält eine Variable den Verweis auf das Objekt . Ein Objekt ist ein Teil des zugewiesenen Speichers, der einen Wert und einen Header enthält . Der Header des Objekts enthält seinen Typ und einen Referenzzähler, der angibt, wie oft auf dieses Objekt im Quellcode verwiesen wird, damit die Garbage Collection erkennen kann, ob ein Objekt erfasst werden kann.

Wenn Sie nun einer Variablen Werte zuweisen, weist Python tatsächlich Referenzen zu, die Zeiger auf Speicherorte sind, die Objekten zugewiesen sind:

# x holds a reference to the memory location allocated for  
# the object(type=string, value="Hello World", refCounter=1)

x = "Hello World" 

Wenn Sie nun derselben Variablen Objekte unterschiedlichen Typs zuweisen, ändern Sie die Referenz tatsächlich so, dass sie auf ein anderes Objekt (dh einen anderen Speicherort) verweist. Wenn Sie einer Variablen eine andere Referenz (und damit ein anderes Objekt) zuweisen, fordert der Garbage Collector den dem vorherigen Objekt zugewiesenen Speicherplatz sofort zurück, sofern keine andere Variable im Quellcode darauf verweist:

# x holds a reference to the memory location allocated for  
# the object(type=string, value="Hello World", refCounter=1)

x = "Hello World" 

# Now x holds the reference to a different object(type=int, value=10, refCounter=1)
# and object(type=string, value="Hello World", refCounter=0) -which is not refereced elsewhere
# will now be garbage-collected.
x = 10

Kommen wir jetzt zu Ihrem Beispiel:

spam enthält den Verweis auf das Objekt (Typ = int, Wert = 42, refCounter = 1):

>>> spam = 42

cheeseEnthält jetzt auch den Verweis auf das Objekt (Typ = int, Wert = 42, refCounter = 2)

>>> cheese = spam

Jetzt enthält Spam einen Verweis auf ein anderes Objekt (Typ = int, Wert = 100, refCounter = 1).

>>> spam = 100
>>> spam
100

Käse zeigt jedoch weiterhin auf das Objekt (Typ = int, Wert = 42, refCounter = 1).

>>> cheese
42
Giorgos Myrianthous
quelle
0

Beim Speichern spam = 42wird ein Objekt im Speicher erstellt. Dann weisen Sie cheese = spam, Sie ordnet das Objekt referenziert durch spamzu cheese. Und schließlich spam = 100ändert sich beim Ändern nur das spamObjekt. Also cheese = 42.

Md. Rezwanul Haque
quelle
7
"Dann weisen Sie Käse = Spam zu, es wird ein anderes Objekt im Speicher erstellt." Nein, das tut es nicht. Er ordnet das Objekt referenziert durch spamzu cheese. Es werden keine neuen Objekte erstellt.
juanpa.arrivillaga
-1

Die Funktionsseite numpy.copy () enthält eine Erklärung

https://docs.scipy.org/doc/numpy/reference/generated/numpy.copy.html

Das Beispiel lautet wie folgt:

Erstellen Sie ein Array x mit einer Referenz y und einer Kopie z:

x = np.array([1, 2, 3])
y = x
z = np.copy(x)

Beachten Sie, dass sich beim Ändern von x y ändert, nicht jedoch z:

x[0] = 10
x[0] == y[0]
True
x[0] == z[0]
False
Onur Tavaslıoğlu
quelle