Sind Python-Variablen Zeiger? oder was sind sie sonst?


82

Variablen in Python sind meines Wissens nur Zeiger.

Basierend auf dieser Regel kann ich davon ausgehen, dass das Ergebnis für dieses Code-Snippet:

i = 5
j = i
j = 3 
print(i)

wäre 3. Aber ich habe ein unerwartetes Ergebnis für mich erzielt 5.

Außerdem behandelt mein Python-Buch dieses Beispiel:

i = [1,2,3]
j = i
i[0] = 5
print(j)

das Ergebnis wäre [5,2,3].

Was verstehe ich falsch?



5
Ich sehe nicht ein, wie ies gleich sein sollte3
Tooniis

@Tooniis j zeigt jetzt auf einen anderen Speicherblock als den, auf den ich zeige. Sie haben es wahrscheinlich inzwischen verstanden, aber nur für den Fall, dass jemand anderes dies sieht
Swaroop Joshi

Antworten:


89

Wir nennen sie Referenzen. Sie arbeiten so

i = 5     # create int(5) instance, bind it to i
j = i     # bind j to the same int as i
j = 3     # create int(3) instance, bind it to j
print i   # i still bound to the int(5), j bound to the int(3)

Kleine Ints werden interniert, aber das ist für diese Erklärung nicht wichtig

i = [1,2,3]   # create the list instance, and bind it to i
j = i         # bind j to the same list as i
i[0] = 5      # change the first item of i
print j       # j is still bound to the same list as i

3
Hallo John, was meinst du mit "Kleine Ints sind interniert"? Vielen Dank!
Yuqli

6
@yuqli In Python ist alles ein Objekt, einschließlich Zahlen. Da kleine Zahlen (-5.256) sehr häufig verwendet werden, werden sie in CPython "interniert" oder zwischengespeichert. Daher 40verweisen Sie bei jeder Eingabe auf dasselbe Objekt im Speicher. Um diesen Typ zu sehen, geben Sie a, b = 256 ein und testen Sie a is b. Versuchen Sie es jetzt mit a, b = 257. Siehe: stackoverflow.com/a/1136852/3973834 und codementor.io/python/tutorial/…
Evan Rosica

3
Nach meiner Erfahrung ist es unter Python-Entwicklern üblicher , sie "Namen" zu nennen. Der Begriff "Referenzen" ist mit unnötigem C-Gepäck verbunden und tendiert Python ( die Sprache ) möglicherweise zu sehr zu CPython ( der Implementierung ), das zufällig die Referenzzählung verwendet.
wim

33

Variablen sind keine Zeiger. Wenn Sie einer Variablen zuweisen, binden Sie den Namen an ein Objekt. Ab diesem Zeitpunkt können Sie mit dem Namen auf das Objekt verweisen, bis dieser Name zurückgebunden wird.

In Ihrem ersten Beispiel ist der Name ian den Wert gebunden 5. Das Binden verschiedener Werte an den Namen jhat keine Auswirkungen auf i. Wenn Sie später drucken, bleibt der Wert des iWerts erhalten 5.

In Ihrem zweiten Beispiel binden Sie beide iund jan dasselbe Listenobjekt. Wenn Sie den Inhalt der Liste ändern, können Sie die Änderung unabhängig davon sehen, mit welchem ​​Namen Sie auf die Liste verweisen.

Beachten Sie, dass es falsch wäre, wenn Sie sagen würden, dass sich beide Listen geändert haben. Es gibt nur eine Liste, aber es gibt zwei Namen ( iund j), die darauf verweisen.

Dazugehörige Dokumentation


15

Python-Variablen sind Namen, die an Objekte gebunden sind

Aus den Dokumenten :

Namen beziehen sich auf Objekte. Namen werden durch Namensbindungsoperationen eingeführt. Jedes Vorkommen eines Namens im Programmtext bezieht sich auf die Bindung dieses Namens, die im innersten Funktionsblock festgelegt ist, der die Verwendung enthält.

Wenn Sie das tun

i = 5
j = i

das ist das gleiche wie:

i = 5
j = 5

jzeigt nicht auf iund jweiß nach der Zuweisung nicht, dass es iexistiert. jist einfach an das gebunden, worauf izum Zeitpunkt der Zuweisung hingewiesen wurde.

Wenn Sie die Aufgaben in derselben Zeile erledigen würden, würde dies folgendermaßen aussehen:

i = j = 5

Und das Ergebnis wäre genau das gleiche.

Also später machen

i = 3

ändert nicht, worauf jzeigt - und Sie können es tauschen - j = 3würde nicht ändern, worauf izeigt.

In Ihrem Beispiel wird der Verweis auf die Liste nicht entfernt

Wenn Sie dies tun:

i = [1,2,3]
j = i

Es ist das gleiche wie das:

i = j = [1,2,3]

so iund jbeide zeigen auf die gleiche Liste. Dann mutiert Ihr Beispiel die Liste:

i[0] = 5

Python-Listen sind veränderbare Objekte. Wenn Sie also die Liste von einer Referenz aus ändern und von einer anderen Referenz aus betrachten, sehen Sie dasselbe Ergebnis, da es sich um dieselbe Liste handelt.


9

TLDR: Python- Namen funktionieren wie Zeiger mit automatischer De- / Referenzierung, erlauben jedoch keine expliziten Zeigeroperationen. Andere Ziele stellen Indirektionen dar, die sich ähnlich wie Zeiger verhalten.


Die CPython-Implementierung verwendet Zeiger vom TypPyObject* unter der Haube. Daher ist es möglich, Namenssemantik in Zeigeroperationen zu übersetzen. Der Schlüssel besteht darin, Namen von tatsächlichen Objekten zu trennen .

Der Beispiel-Python-Code enthält sowohl Namen ( i) als auch Objekte ( 5).

i = 5  # name `i` refers to object `5`
j = i  # ???
j = 3  # name `j` refers to object `3`

Dies kann grob in C-Code mit separaten Namen und Objekten übersetzt werden.

int three=3, five=5;  // objects
int *i, *j;           // names
i = &five;   // name `i` refers to position of object `5`
j = i;       // name `j` refers to referent of `i`
j = &three;  // name `j` refers to position of object `3`

Der wichtige Teil ist, dass "Namen als Zeiger" keine Objekte speichern! Wir haben nicht definiert *i = five, aber i = &five. Die Namen und Objekte existieren unabhängig voneinander.

Namen verweisen nur auf vorhandene Objekte im Speicher.

Bei der Zuweisung von Name zu Name werden keine Objekte ausgetauscht! Wenn wir definieren j = i, ist dies äquivalent zu j = &five. Weder inoch jsind mit dem anderen verbunden.

+- name i -+ -\
               \
                --> + <five> -+
               /    |        5 |
+- name j -+ -/     +----------+

Das Ändern des Ziels eines Namens wirkt sich daher nicht auf den anderen aus . Es wird nur aktualisiert, worauf dieser bestimmte Name verweist.


Python hat auch andere Arten von namenähnlichen Elementen : Attributreferenzen ( i.j), Abonnements ( i[j]) und Slicing ( i[:j]). Im Gegensatz zu Namen, die sich direkt auf Objekte beziehen, beziehen sich alle drei indirekt auf Elemente von Objekten.

Der Beispielcode enthält sowohl Namen ( i) als auch ein Abonnement ( i[0]).

i = [1,2,3]  # name `i` refers to object `[1, 2, 3]`
j = i        # name `j` refers to referent of `i`
i[0] = 5     # ???

Ein CPython listverwendet ein C-Array von PyObject*Zeigern unter der Haube. Dies kann wiederum grob in C-Code mit separaten Namen und Objekten übersetzt werden.

typedef struct{
    int *elements[3];
} list;  // length 3 `list` type

int one = 1, two = 2, three = 3, five = 5;
list values = {&one, &two, &three};  // objects
list *i, *j;                         // names
i = &values;             // name `i` refers to object `[1, 2, 3]`
j = i;                   // name `j` refers to referent of `i`
i->elements[0] = &five;  // leading element of `i` refers to object `5`

Wichtig ist, dass wir keine Namen geändert haben! Wir haben i->elements[0]das Element eines Objekts geändert , auf das unsere beiden Namen verweisen.

Die Werte vorhandener zusammengesetzter Objekte können geändert werden.

Wenn Sie den Wert eines Objekts über einen Namen ändern, werden die Namen nicht geändert. Beide iund jbeziehen sich immer noch auf dasselbe Objekt, dessen Wert wir ändern können.

+- name i -+ -\
               \
                --> + <values> -+
               /    |  elements | --> [1, 2, 3]
+- name j -+ -/     +-----------+

Das Zwischenobjekt verhält sich insofern ähnlich wie ein Zeiger, als wir direkt ändern können, worauf es zeigt, und es aus mehreren Namen referenzieren können.


1
Ich mag diese Antwort, aber ich glaube , Sie die invertierten iund jZuweisungen in Ihrem Beispiel. Sie beginnen mit i = 5, j = 3und sie dann in den Rest Ihres Post invertieren. Dies ist jedoch die einzige Antwort, die der Frage im OP gerecht wird und wirklich erklärt, was unter der Haube passiert.
Jeremy Radcliff

1
@jeremyradcliff Danke für das Heads Up. Sollte jetzt behoben sein. Lassen Sie mich wissen, wenn ich noch etwas verpasst habe.
MisterMiyagi

7

Sie sind keine Zeiger, sondern Verweise auf Objekte. Objekte können entweder veränderlich oder unveränderlich sein. Ein unveränderliches Objekt wird kopiert, wenn es geändert wird. Ein veränderliches Objekt wird an Ort und Stelle geändert. Eine Ganzzahl ist ein unveränderliches Objekt, auf das Sie mit Ihren Variablen i und j verweisen. Eine Liste ist ein veränderbares Objekt.

In Ihrem ersten Beispiel

i=5
# The label i now references 5
j=i
# The label j now references what i references
j=3
# The label j now references 3
print i
# i still references 5

In Ihrem zweiten Beispiel:

i=[1,2,3]
# i references a list object (a mutable object)
j=i
# j now references the same object as i (they reference the same mutable object)
i[0]=5
# sets first element of references object to 5
print j
# prints the list object that j references. It's the same one as i.

"Ein unveränderliches Objekt wird kopiert, wenn es geändert wird." Das ist ein bisschen widersprüchlich.
PM 2Ring

1

Wenn Sie j=3die Beschriftung festlegenj nicht mehr gilt (Punkte), izeigt sie auf die Ganzzahl 3. Der Name ibezieht sich immer noch auf den ursprünglich festgelegten Wert 5.


1

Jede Variable auf der linken Seite des Zeichens '=' wird mit dem Wert auf der rechten Seite von '=' versehen.

i = 5

j = i --- j hat 5

j = 3 --- j hat 3 (überschreibt den Wert von 5), aber in Bezug auf i wurde nichts geändert

print(i)- so druckt dies 5


1

Durch die Zuweisung werden keine Objekte geändert. Alles, was es tut, ist zu ändern, wo die Variable zeigt. Wenn Sie ändern, wo eine Variable zeigt, ändert sich nicht, wo eine andere zeigt.

Sie denken wahrscheinlich an die Tatsache, dass Listen und Wörterbücher veränderbare Typen sind. Es gibt Operatoren, mit denen die tatsächlichen Objekte an Ort und Stelle geändert werden können. Wenn Sie eines dieser Objekte verwenden, wird die Änderung in allen Variablen angezeigt, die auf dasselbe Objekt verweisen:

x = []
y = x
x.append(1)
# x and y both are now [1]

Aber die Zuweisung bewegt immer noch nur den Zeiger:

x = [2]
# x now points to new list [2]; y still points to old list [1]

Zahlen sind im Gegensatz zu Wörterbüchern und Listen unveränderlich. Wenn Sie dies tun x = 3; x += 2, wandeln Sie die Nummer 3 nicht in die Nummer 5 um. Sie machen xstattdessen nur den variablen Punkt auf 5. Die 3 ist immer noch unverändert da draußen, und alle Variablen, die darauf zeigen, sehen immer noch 3 als ihren Wert.

(In der tatsächlichen Implementierung sind Zahlen wahrscheinlich überhaupt keine Referenztypen. Es ist wahrscheinlicher, dass die Variablen tatsächlich eine Darstellung des Werts direkt enthalten, anstatt darauf zu verweisen. Dieses Implementierungsdetail ändert jedoch nichts an der Semantik, wenn es um unveränderliche Typen geht .)


1
Das ist nicht was Werttyp bedeutet. Werttyp bedeutet genau das, was Sie im letzten Absatz beschrieben haben (dass der Wert anstelle eines Verweises auf das Objekt übergeben / kopiert wird), und es ist intern nicht so (in CPython und im PyPy sans JIT-Compiler - jede Ganzzahl ist eine Heap-zugewiesenes Objekt). Bleib einfach bei unveränderlich, das ist genau das Wort, das du dort brauchst.

-1

In Python ist alles ein Objekt, einschließlich der Speicherelemente selbst, die Sie zurückgeben. Das heißt, wenn ein neuer Speicherblock erstellt wird (unabhängig davon, was Sie erstellt haben: int, str, benutzerdefiniertes Objekt usw.), haben Sie ein neues Speicherobjekt. In Ihrem Fall ist dies die Zuordnung zu 3, die ein neues (Speicher-) Objekt erstellt und somit eine neue Adresse hat.

Wenn Sie Folgendes ausführen, sehen Sie leicht, was ich meine.

i = 5
j = i
print("id of j: {}", id(j))
j = 3
print("id of j: {}", id(j))

IMO, was das Gedächtnis betrifft, ist dies das Schlüsselverständnis / der Unterschied zwischen C und Python. In C / C ++ erhalten Sie anstelle eines Speicherobjekts einen Speicherzeiger (wenn Sie natürlich die Zeigersyntax verwenden), der Ihnen mehr Flexibilität beim Ändern der angegebenen Adresse bietet.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.