Frage Wie übertrage ich eine Variable als Referenz?


Die Python-Dokumentation scheint unklar zu sein, ob Parameter als Referenz oder Wert übergeben werden, und der folgende Code erzeugt den unveränderten Wert 'Original'

class PassByReference:
    def __init__(self):
        self.variable = 'Original'
        self.change(self.variable)
        print(self.variable)

    def change(self, var):
        var = 'Changed'

Kann ich etwas tun, um die Variable durch tatsächliche Referenz zu übergeben?


2053
2018-06-12 10:23


Ursprung


Antworten:


Argumente sind bestanden durch Zuordnung. Die Begründung dafür ist zweifach:

  1. Der übergebene Parameter ist eigentlich a Referenz an ein Objekt (aber die Referenz wird als Wert übergeben)
  2. Einige Datentypen sind veränderbar, andere nicht

Damit:

  • Wenn du einen passierst veränderlich Object in eine Methode, die Methode erhält einen Verweis auf das gleiche Objekt und Sie können es nach Herzenslust mutieren, aber wenn Sie die Referenz in der Methode erneut binden, wird der äußere Bereich nichts davon wissen, und nachdem Sie fertig sind, Die äußere Referenz zeigt immer noch auf das ursprüngliche Objekt.

  • Wenn du ein passierst unveränderlich Objekt zu einer Methode, Sie können die äußere Referenz immer noch nicht binden, und Sie können nicht einmal das Objekt mutieren.

Um es noch klarer zu machen, lassen Sie uns einige Beispiele nennen.

Liste - ein veränderbarer Typ

Lassen Sie uns versuchen, die Liste zu ändern, die an eine Methode übergeben wurde:

def try_to_change_list_contents(the_list):
    print('got', the_list)
    the_list.append('four')
    print('changed to', the_list)

outer_list = ['one', 'two', 'three']

print('before, outer_list =', outer_list)
try_to_change_list_contents(outer_list)
print('after, outer_list =', outer_list)

Ausgabe:

before, outer_list = ['one', 'two', 'three']
got ['one', 'two', 'three']
changed to ['one', 'two', 'three', 'four']
after, outer_list = ['one', 'two', 'three', 'four']

Da der übergebene Parameter ein Verweis auf ist outer_list, nicht eine Kopie davon, können wir die mutierenden Listenmethoden verwenden, um sie zu ändern und die Änderungen im äußeren Bereich widerzuspiegeln.

Sehen wir uns nun an, was passiert, wenn wir versuchen, die Referenz zu ändern, die als Parameter übergeben wurde:

def try_to_change_list_reference(the_list):
    print('got', the_list)
    the_list = ['and', 'we', 'can', 'not', 'lie']
    print('set to', the_list)

outer_list = ['we', 'like', 'proper', 'English']

print('before, outer_list =', outer_list)
try_to_change_list_reference(outer_list)
print('after, outer_list =', outer_list)

Ausgabe:

before, outer_list = ['we', 'like', 'proper', 'English']
got ['we', 'like', 'proper', 'English']
set to ['and', 'we', 'can', 'not', 'lie']
after, outer_list = ['we', 'like', 'proper', 'English']

Seit der the_list Der Parameter wurde als Wert übergeben, und die Zuweisung einer neuen Liste hatte keinen Effekt, den der Code außerhalb der Methode sehen konnte. Das the_list war eine Kopie der outer_list Referenz, und wir hatten the_list zeigen Sie auf eine neue Liste, aber es gab keine Möglichkeit, wo zu ändern outer_list spitz.

String - ein unveränderlicher Typ

Es ist unveränderlich, also können wir nichts tun, um den Inhalt der Zeichenfolge zu ändern

Versuchen wir nun, die Referenz zu ändern

def try_to_change_string_reference(the_string):
    print('got', the_string)
    the_string = 'In a kingdom by the sea'
    print('set to', the_string)

outer_string = 'It was many and many a year ago'

print('before, outer_string =', outer_string)
try_to_change_string_reference(outer_string)
print('after, outer_string =', outer_string)

Ausgabe:

before, outer_string = It was many and many a year ago
got It was many and many a year ago
set to In a kingdom by the sea
after, outer_string = It was many and many a year ago

Wiederum seit dem the_string Der Parameter wurde als Wert übergeben. Wenn Sie ihm einen neuen String zuweisen, hat dies keinen Effekt, den der Code außerhalb der Methode sehen könnte. Das the_string war eine Kopie der outer_string Referenz, und wir hatten the_string zeigen auf eine neue Zeichenfolge, aber es gab keine Möglichkeit, wo zu ändern outer_string spitz.

Ich hoffe, dass dies die Dinge ein wenig aufklärt.

BEARBEITEN: Es wurde festgestellt, dass dies nicht die Frage beantwortet, dass @David ursprünglich fragte: "Gibt es etwas, was ich tun kann, um die Variable durch tatsächliche Referenz zu übergeben?". Lass uns daran arbeiten.

Wie kommen wir damit klar?

Wie @ Andreas Antwort zeigt, könnten Sie den neuen Wert zurückgeben. Dies ändert nichts an der Art und Weise, wie Dinge weitergegeben werden, aber Sie können die gewünschten Informationen wieder erhalten:

def return_a_whole_new_string(the_string):
    new_string = something_to_do_with_the_old_string(the_string)
    return new_string

# then you could call it like
my_string = return_a_whole_new_string(my_string)

Wenn Sie wirklich vermeiden wollten, einen Rückgabewert zu verwenden, könnten Sie eine Klasse erstellen, die Ihren Wert enthält, und sie an die Funktion übergeben oder eine vorhandene Klasse wie eine Liste verwenden:

def use_a_wrapper_to_simulate_pass_by_reference(stuff_to_change):
    new_string = something_to_do_with_the_old_string(stuff_to_change[0])
    stuff_to_change[0] = new_string

# then you could call it like
wrapper = [my_string]
use_a_wrapper_to_simulate_pass_by_reference(wrapper)

do_something_with(wrapper[0])

Obwohl dies ein wenig umständlich erscheint.


2221
2018-06-12 11:18



Das Problem rührt von einem Missverständnis der Variablen in Python her. Wenn Sie mit den meisten traditionellen Sprachen vertraut sind, haben Sie ein mentales Modell dessen, was in der folgenden Reihenfolge passiert:

a = 1
a = 2

Sie glauben, dass a ist ein Speicherort, der den Wert speichert 1, dann wird aktualisiert, um den Wert zu speichern 2. So funktionieren die Dinge in Python nicht. Lieber, a Beginnt als Referenz auf ein Objekt mit dem Wert 1, wird dann als Referenz auf ein Objekt mit dem Wert neu zugewiesen 2. Diese beiden Objekte können weiterhin nebeneinander existieren a bezieht sich nicht mehr auf den ersten; In der Tat können sie durch eine beliebige Anzahl von anderen Referenzen innerhalb des Programms geteilt werden.

Wenn Sie eine Funktion mit einem Parameter aufrufen, wird eine neue Referenz erstellt, die auf das übergebene Objekt verweist. Dies ist getrennt von der Referenz, die im Funktionsaufruf verwendet wurde. Daher gibt es keine Möglichkeit, diese Referenz zu aktualisieren und auf a zu verweisen neues Objekt In Ihrem Beispiel:

def __init__(self):
    self.variable = 'Original'
    self.Change(self.variable)

def Change(self, var):
    var = 'Changed'

self.variable ist eine Referenz auf das String-Objekt 'Original'. Wenn du anrufst Change Sie erstellen eine zweite Referenz var zum Objekt. Innerhalb der Funktion weisen Sie die Referenz neu zu var zu einem anderen String-Objekt 'Changed', aber die Referenz self.variable ist getrennt und ändert sich nicht.

Der einzige Weg, dies zu umgehen, besteht darin, ein veränderbares Objekt zu übergeben. Da sich beide Referenzen auf dasselbe Objekt beziehen, werden alle Änderungen am Objekt an beiden Stellen widergespiegelt.

def __init__(self):         
    self.variable = ['Original']
    self.Change(self.variable)

def Change(self, var):
    var[0] = 'Changed'

529
2017-11-15 17:45



Ich fand die anderen Antworten ziemlich lang und kompliziert, also habe ich dieses einfache Diagramm erstellt, um zu erklären, wie Python Variablen und Parameter behandelt. enter image description here


242
2017-09-04 16:05



Es ist weder "pass-by-value" noch "pass-by-reference" - es ist call-by-object. Sehen Sie dies, von Fredrik Lundh:

http://effbot.org/zone/call-by-object.htm

Hier ist ein wichtiges Zitat:

"... Variablen [Namen] sind nicht Objekte; Sie können nicht mit anderen Variablen bezeichnet werden oder auf Objekte verweisen. "

In Ihrem Beispiel, wenn die Change Methode heißt - a Namensraum ist dafür geschaffen; und var wird ein Name innerhalb dieses Namensraums für das String-Objekt 'Original'. Dieses Objekt hat dann einen Namen in zwei Namespaces. Nächster, var = 'Changed' bindet var zu einem neuen String-Objekt, und damit der Namespace der Methode vergisst 'Original'. Schließlich ist dieser Namespace vergessen und die Zeichenfolge 'Changed' zusammen mit ihm.


219
2018-06-12 12:55



Denken Sie an Dinge, die bestanden werden durch Zuweisung statt nach Referenz / nach Wert. Auf diese Weise ist es immer klar, was passiert, solange Sie verstehen, was während des normalen Einsatzes passiert.

Wenn Sie eine Liste an eine Funktion / Methode übergeben, wird die Liste dem Parameternamen zugewiesen. Durch Anhängen an die Liste wird die Liste geändert. Erneutes Zuordnen der Liste Innerhalb Die Funktion ändert die ursprüngliche Liste nicht, da:

a = [1, 2, 3]
b = a
b.append(4)
b = ['a', 'b']
print a, b      # prints [1, 2, 3, 4] ['a', 'b']

Da unveränderliche Typen nicht geändert werden können, sind sie scheinen wie an Wert übergeben werden - das Übergeben eines int in eine Funktion bedeutet das Zuweisen des int zum Parameter functions. Sie können das immer nur neu zuweisen, aber es wird den ursprünglichen Variablenwert nicht ändern.


142
2018-06-12 12:17



Effbot (aka Fredrik Lundh) hat Pythons Variablenübergabestil als call-by-object beschrieben: http://effbot.org/zone/call-by-object.htm

Objekte werden auf dem Heap zugeordnet und Zeiger auf sie können überall herumgereicht werden.

  • Wenn Sie eine Aufgabe wie z x = 1000Es wird ein Wörterbucheintrag erstellt, der die Zeichenfolge "x" im aktuellen Namespace einem Zeiger auf das Integer-Objekt mit eintausend Elementen zuordnet.

  • Wenn Sie "x" mit aktualisieren x = 2000, ein neues Ganzzahlobjekt wird erstellt und das Wörterbuch wird aktualisiert, um auf das neue Objekt zu zeigen. Das alte eintausend Objekt ist unverändert (und kann oder kann nicht am Leben sein, abhängig davon, ob sich irgendetwas anderes auf das Objekt bezieht).

  • Wenn Sie eine neue Aufgabe wie z y = x, wird ein neuer Wörterbucheintrag "y" erzeugt, der auf das gleiche Objekt wie der Eintrag für "x" zeigt.

  • Objekte wie Strings und Ganzzahlen sind unveränderlich. Das bedeutet einfach, dass es keine Methoden gibt, die das Objekt nach der Erstellung ändern können. Zum Beispiel, sobald das Integer-Objekt eintausend erstellt wird, wird es nie ändern. Math wird erstellt, indem neue Integer-Objekte erstellt werden.

  • Objekte wie Listen sind veränderlich. Dies bedeutet, dass der Inhalt des Objekts durch alles geändert werden kann, das auf das Objekt zeigt. Beispielsweise, x = []; y = x; x.append(10); print y wird gedruckt [10]. Die leere Liste wurde erstellt. Sowohl "x" als auch "y" zeigen auf dieselbe Liste. Das anhängen Die Methode mutiert (aktualisiert) das Listenobjekt (z. B. das Hinzufügen eines Datensatzes zu einer Datenbank), und das Ergebnis ist sowohl für "x" als auch für "y" sichtbar (genauso wie eine Datenbankaktualisierung für jede Verbindung zu dieser Datenbank sichtbar wäre).

Ich hoffe, das klärt das Problem für Sie.


53
2018-03-29 04:41



Technisch, Python verwendet immer Werte für die Übergabe als Referenz. Ich werde es wiederholen meine andere Antwort um meine Aussage zu unterstützen.

Python verwendet immer Pass-by-Reference-Werte. Es gibt keine Ausnahme. Jede variable Zuweisung bedeutet, dass der Referenzwert kopiert wird. Keine Ausnahmen. Jede Variable ist der Name, der an den Referenzwert gebunden ist. Immer.

Sie können sich einen Referenzwert als Adresse des Zielobjekts vorstellen. Die Adresse wird bei Verwendung automatisch dereferenziert. Auf diese Weise arbeiten Sie mit dem Referenzwert und arbeiten direkt mit dem Zielobjekt. Aber es gibt immer eine Referenz dazwischen, einen Schritt mehr, um zum Ziel zu springen.

Hier ist das Beispiel, das beweist, dass Python die Übergabe als Referenz verwendet:

Illustrated example of passing the argument

Wenn das Argument nach Wert übergeben wurde, wird das äußere Argument verwendet lst konnte nicht geändert werden. Das Grün ist das Zielobjekt (das Schwarz ist der Wert, der in ihm gespeichert wird, das Rot ist der Objekttyp), das Gelb ist der Speicher mit dem Bezugswert im Inneren - gezeichnet als Pfeil. Der blaue durchgezogene Pfeil ist der Referenzwert, der an die Funktion übergeben wurde (über den gestrichelten blauen Pfeilpfad). Das hässliche dunkle Gelb ist das interne Wörterbuch. (Es könnte tatsächlich auch als grüne Ellipse gezeichnet werden. Die Farbe und die Form sagt nur, dass es intern ist.)

Du kannst den ... benutzen id() eingebaute Funktion, um zu erfahren, was der Referenzwert ist (dh die Adresse des Zielobjekts).

In kompilierten Sprachen ist eine Variable ein Speicherbereich, der den Wert des Typs erfassen kann. In Python ist eine Variable ein Name (intern als Zeichenfolge erfasst), der an die Referenzvariable gebunden ist, die den Referenzwert für das Zielobjekt enthält. Der Name der Variablen ist der Schlüssel im internen Wörterbuch, der Wertteil dieses Wörterbuchelements speichert den Referenzwert für das Ziel.

Referenzwerte sind in Python verborgen. Es gibt keinen expliziten Benutzertyp zum Speichern des Referenzwerts. Sie können jedoch ein Listenelement (oder ein Element in einem anderen geeigneten Containertyp) als Referenzvariable verwenden, da alle Container die Elemente auch als Referenzen auf die Zielobjekte speichern. Mit anderen Worten, Elemente sind tatsächlich nicht innerhalb des Containers enthalten - nur die Referenzen auf Elemente sind.


53
2017-09-15 18:53



Ein einfacher Trick, den ich normalerweise benutze, besteht darin, ihn einfach in eine Liste zu schreiben:

def Change(self, var):
    var[0] = 'Changed'

variable = ['Original']
self.Change(variable)      
print variable[0]

(Ja, ich weiß, das kann unbequem sein, aber manchmal ist es einfach genug, dies zu tun.)


36
2017-08-05 22:52



(Bearbeiten - Blair hat seine enorm beliebte Antwort aktualisiert, so dass sie nun korrekt ist)

Ich denke, es ist wichtig zu beachten, dass der aktuelle Posten mit den meisten Stimmen (von Blair Conrad), obwohl er in Bezug auf sein Ergebnis korrekt ist, irreführend ist und aufgrund seiner Definitionen grenzwertig falsch ist. Während es viele Sprachen gibt (wie C), die es dem Benutzer erlauben, entweder als Verweis oder als Wert zu übergeben, gehört Python nicht dazu.

David Cournapeaus Antwort zeigt auf die wirkliche Antwort und erklärt, warum das Verhalten in Blair Conrads Beitrag korrekt zu sein scheint, während die Definitionen nicht stimmen.

In dem Maße, in dem Python als Wert übergeben wird, werden alle Sprachen als Werte übergeben, da einige Daten (sei es ein "Wert" oder eine "Referenz") gesendet werden müssen. Das bedeutet jedoch nicht, dass Python in dem Sinne, dass ein C-Programmierer darüber nachdenkt, Wert für Wert ist.

Wenn Sie das Verhalten wollen, ist Blair Conrads Antwort in Ordnung. Aber wenn Sie wissen wollen, warum Python weder nach Wert noch nach Referenz besteht, lesen Sie David Cournapeaus Antwort.


34
2018-06-27 12:33



In Python gibt es keine Variablen

Der Schlüssel zum Verständnis der Parameterübergabe besteht darin, nicht mehr über "Variablen" nachzudenken. Es gibt Namen und Objekte in Python und zusammen sind sie erscheinen wie Variablen, aber es ist nützlich, immer die drei zu unterscheiden.

  1. Python hat Namen und Objekte.
  2. Zuweisung bindet einen Namen an ein Objekt.
  3. Das Übergeben eines Arguments an eine Funktion bindet auch einen Namen (den Parameternamen der Funktion) an ein Objekt.

Das ist alles, was dazu gehört. Mutabilität ist für diese Frage irrelevant.

Beispiel:

a = 1

Dies bindet den Namen a zu einem Objekt vom Typ Ganzzahl, das den Wert 1 enthält.

b = x

Dies bindet den Namen b zu dem gleichen Objekt wie der Name x ist derzeit verpflichtet. Danach der Name b hat nichts mit dem Namen zu tun x nicht mehr.

Siehe Abschnitte 3.1 und 4.2 in der Python 3-Sprachreferenz.


Also in dem Code in der Frage gezeigt, die Aussage self.Change(self.variable) bindet den Namen var (im Rahmen der Funktion Change) zu dem Objekt, das den Wert enthält 'Original' und die Zuordnung var = 'Changed' (im Körper der Funktion Change) weist denselben Namen erneut zu: an ein anderes Objekt (das zufälligerweise ebenfalls eine Zeichenfolge enthält, aber auch etwas anderes sein könnte).


25
2018-02-11 11:29