3290 Stimmen

Wie übergebe ich eine Variable per Referenz?

Werden die Parameter als Referenz oder als Wert übergeben? Wie übergebe ich per Referenz, so dass der folgende Code Folgendes ausgibt 'Changed' anstelle von 'Original' ?

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

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

32 Stimmen

Eine kurze Erklärung/Klarstellung finden Sie in der ersten Antwort auf diese Stackoverflow-Frage . Da Zeichenketten unveränderlich sind, werden sie nicht geändert und es wird eine neue Variable erstellt, so dass die "äußere" Variable immer noch denselben Wert hat.

11 Stimmen

Der Code in BlairConrads Antwort ist gut, aber die Erklärung von DavidCournapeau und DarenThomas ist richtig.

79 Stimmen

Bevor Sie die ausgewählte Antwort lesen, sollten Sie diesen kurzen Text lesen Andere Sprachen haben "Variablen", Python hat "Namen". . Denken Sie an "Namen" und "Objekte" anstelle von "Variablen" und "Referenzen" und Sie sollten viele ähnliche Probleme vermeiden.

3525voto

Blair Conrad Punkte 217777

Argumente sind durch Abtretung übergeben . Dahinter stehen zwei Überlegungen:

  1. der übergebene Parameter ist eigentlich ein Referenz zu einem Objekt (aber die Referenz wird als Wert übergeben)
  2. einige Datentypen sind veränderbar, andere nicht

Donc :

  • Wenn Sie eine änderbar Objekt in eine Methode einbinden, erhält die Methode einen Verweis auf dasselbe Objekt und Sie können es nach Herzenslust verändern, aber wenn Sie den Verweis in der Methode neu binden, weiß der äußere Bereich nichts davon, und nachdem Sie fertig sind, zeigt der äußere Verweis immer noch auf das ursprüngliche Objekt.

  • Wenn Sie eine unveränderlich Objekt an eine Methode übergeben, können Sie die äußere Referenz immer noch nicht neu binden, und Sie können das Objekt nicht einmal ändern.

Um es noch deutlicher zu machen, hier einige Beispiele.

Liste - ein veränderlicher Typ

Versuchen wir, 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 outer_list und nicht eine Kopie davon ist, können wir die Methoden der mutierenden Liste verwenden, um sie zu ändern und die Änderungen im äußeren Bereich wiederzugeben.

Nun wollen wir sehen, was passiert, wenn wir versuchen, den als Parameter übergebenen Verweis zu ändern:

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']

Da die the_list Parameter als Wert übergeben wurde, hatte die Zuweisung einer neuen Liste keine für den Code außerhalb der Methode sichtbaren Auswirkungen. Die the_list war eine Kopie des outer_list Referenz, und wir hatten the_list auf eine neue Liste verweisen, aber es gab keine Möglichkeit zu ändern, wo outer_list spitz.

String - ein unveränderlicher Typ

Sie ist unveränderlich, wir können also nichts tun, um den Inhalt der Zeichenkette 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

Auch hier gilt, dass die the_string Parameter als Wert übergeben wurde, hatte die Zuweisung einer neuen Zeichenkette keinen für den Code außerhalb der Methode sichtbaren Effekt. Die the_string war eine Kopie des outer_string Referenz, und wir hatten the_string auf eine neue Zeichenkette verweisen, aber es gab keine Möglichkeit zu ändern, wo outer_string spitz.

Ich hoffe, das klärt die Sache ein wenig auf.

EDITAR: Es wurde festgestellt, dass dies keine Antwort auf die Frage gibt, die @David ursprünglich gestellt hat: "Kann ich etwas tun, um die Variable per Referenz zu übergeben?". Lassen Sie uns daran arbeiten.

Wie können wir das umgehen?

Wie die Antwort von @Andrea zeigt, können Sie den neuen Wert zurückgeben. Dies ändert nichts an der Art und Weise, wie die Dinge übergeben werden, ermöglicht es Ihnen aber, die gewünschten Informationen zurückzubekommen:

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 die Verwendung eines Rückgabewerts wirklich vermeiden wollten, könnten Sie eine Klasse erstellen, die Ihren Wert aufnimmt und ihn an die Funktion weitergibt, oder eine vorhandene Klasse, z. B. 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])

Dies scheint jedoch etwas umständlich zu sein.

207 Stimmen

Das Gleiche gilt für C. Wenn Sie "by reference" übergeben, übergeben Sie eigentlich nach Wert die Referenz... Definieren Sie "durch Verweis" :P

125 Stimmen

Ich bin mir nicht sicher, ob ich Ihre Begriffe verstehe. Ich bin schon eine Weile aus der C-Welt raus, aber damals, als ich noch dabei war, gab es kein "pass by reference" - man konnte Dinge übergeben, und zwar immer "pass by value", d.h. was immer in der Parameterliste stand, wurde kopiert. Aber manchmal war das Ding ein Zeiger, dem man zu dem Stück Speicher (Primitiv, Array, Struktur, was auch immer) folgen konnte, aber man konnte den Zeiger, der aus dem äußeren Bereich kopiert wurde, nicht ändern - wenn man mit der Funktion fertig war, zeigte der ursprüngliche Zeiger immer noch auf dieselbe Adresse. C++ führte Referenzen ein, die sich anders verhielten.

4 Stimmen

@andrea, nein, es ist überhaupt nicht wie C. Conrad hat recht, aber die Begriffe Referenz/Werte sind in Python verwirrend. Deshalb sollte man wirklich einen anderen Begriff verwenden (siehe meinen Link zu effbot für eine gute Erklärung)

891voto

Mark Ransom Punkte 283960

Das Problem rührt von einem Missverständnis darüber her, was Variablen in Python sind. Wenn Sie an die meisten traditionellen Sprachen gewöhnt sind, haben Sie ein mentales Modell davon, was in der folgenden Sequenz passiert:

a = 1
a = 2

Sie glauben, dass a ist ein Speicherplatz, der den Wert 1 wird dann aktualisiert, um den Wert 2 . Das ist nicht die Art und Weise, wie die Dinge in Python funktionieren. Vielmehr, a beginnt als Verweis auf ein Objekt mit dem Wert 1 und wird dann als Verweis auf ein Objekt mit folgendem Wert neu zugewiesen 2 . Diese beiden Objekte können weiterhin nebeneinander bestehen, auch wenn a verweist nicht mehr auf den ersten; vielmehr können sie von einer beliebigen Anzahl anderer Verweise innerhalb des Programms gemeinsam genutzt werden.

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

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

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

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

Die einzige Möglichkeit, dies zu umgehen, besteht darin, ein veränderbares Objekt zu übergeben. Da beide Verweise auf dasselbe Objekt verweisen, werden alle Änderungen an dem Objekt an beiden Stellen berücksichtigt.

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

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

132 Stimmen

Gute, kurze Erklärung. Ihr Absatz "Wenn Sie eine Funktion aufrufen..." ist eine der besten Erklärungen, die ich zu der eher kryptischen Formulierung "Python-Funktionsparameter sind Referenzen, die als Wert übergeben werden" gehört habe. Ich denke, wenn man nur diesen Absatz versteht, ergibt alles andere einen Sinn und ist eine logische Schlussfolgerung daraus. Dann muss man sich nur noch bewusst sein, wann man ein neues Objekt erstellt und wann man ein bestehendes ändert.

4 Stimmen

Aber wie können Sie die Referenz neu zuweisen? Ich dachte, dass man die Adresse von 'var' nicht ändern kann, sondern dass die Zeichenkette "Changed" nun in der Speicheradresse von 'var' gespeichert wird. Nach Ihrer Beschreibung sieht es so aus, als ob "Changed" und "Original" stattdessen an verschiedenen Stellen im Speicher liegen und Sie "var" einfach auf eine andere Adresse umstellen. Ist das richtig?

11 Stimmen

@Glassjawed, ich glaube, du hast es verstanden. "Changed" und "Original" sind zwei verschiedene String-Objekte an unterschiedlichen Speicheradressen und "var" zeigt nicht mehr auf das eine, sondern auf das andere.

461voto

Zenadix Punkte 13273

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

4 Stimmen

Macht es leicht, den subtilen Unterschied zu erkennen, dass es eine Zwischenaufgabe gibt, die für einen zufälligen Betrachter nicht offensichtlich ist. +1

13 Stimmen

Es spielt keine Rolle, ob A veränderbar ist oder nicht. Wenn Sie B etwas anderes zuweisen, A ändert sich nicht . Wenn ein Objekt veränderbar ist, kann man es verändern, klar. Aber das hat nichts mit der direkten Zuordnung zu einem Namen zu tun

2 Stimmen

@Martijn Du hast Recht. Ich habe den Teil der Antwort entfernt, in dem die Veränderbarkeit erwähnt wird. Ich glaube nicht, dass es jetzt noch einfacher werden kann.

277voto

David Cournapeau Punkte 74679

Es handelt sich weder um "pass-by-value" noch um "pass-by-reference" - es ist "call-by-object". Siehe dies, von Fredrik Lundh:

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

Hier ist ein wichtiges Zitat:

"...Variablen [Namen] sind no Objekte; sie können nicht durch andere Variablen bezeichnet oder durch Objekte referenziert werden."

In Ihrem Beispiel, wenn die Change Methode aufgerufen wird - eine Namensraum für sie erstellt wird; und var wird innerhalb dieses Namensraums zu einem Namen für das String-Objekt 'Original' . Dieses Objekt hat dann einen Namen in zwei Namespaces. Nächste, var = 'Changed' bindet var in ein neues String-Objekt umzuwandeln, und so vergisst der Namensraum der Methode die 'Original' . Schließlich wird dieser Namensraum vergessen, und die Zeichenkette 'Changed' mit sich bringen.

25 Stimmen

Ich finde es schwer zu kaufen. Für mich ist das genauso wie bei Java, die Parameter sind Zeiger auf Objekte im Speicher, und diese Zeiger werden über den Stack oder über Register übergeben.

10 Stimmen

Das ist nicht wie bei Java. Einer der Fälle, in denen es nicht dasselbe ist, sind unveränderliche Objekte. Denken Sie an die triviale Funktion lambda x: x. Wenden Sie diese für x = [1, 2, 3] und x = (1, 2, 3) an. Im ersten Fall wird der zurückgegebene Wert eine Kopie der Eingabe sein, im zweiten Fall ist er identisch.

34 Stimmen

Nein, es ist genau wie die Semantik von Java für Objekte. Ich bin mir nicht sicher, was Sie mit "Im ersten Fall ist der zurückgegebene Wert eine Kopie der Eingabe und im zweiten Fall identisch" meinen, aber diese Aussage scheint schlichtweg falsch zu sein.

225voto

Daren Thomas Punkte 64742

Denken Sie an Dinge, die weitergegeben werden durch Abtretung statt durch Verweis/nach Wert. Auf diese Weise ist immer klar, was passiert, solange man versteht, was während der normalen Zuweisung passiert.

Wenn also eine Liste an eine Funktion/Methode übergeben wird, wird die Liste dem Parameternamen zugeordnet. Das Anhängen an die Liste führt dazu, dass die Liste geändert wird. Neuzuweisung der Liste innerhalb wird die Funktion die ursprüngliche Liste nicht verändern, 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 die Übergabe nach Wert - die Übergabe eines int in eine Funktion bedeutet, dass der int dem Funktionsparameter zugewiesen wird. Die Zuweisung kann immer nur neu erfolgen, ändert aber nicht den Wert der ursprünglichen Variablen.

11 Stimmen

Auf den ersten Blick scheint diese Antwort an der ursprünglichen Frage vorbeizugehen. Nach dem zweiten Lesen ist mir klar geworden, dass sie die Sache ziemlich klar macht. Eine gute Fortsetzung dieses Konzepts der "Namensgebung" finden Sie hier: Code wie ein Pythonista: Idiomatisches Python

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X