Warum kann eine Funktion einige Argumente ändern, die vom Aufrufer wahrgenommen werden, andere jedoch nicht?


180

Ich versuche, Pythons Ansatz für den variablen Bereich zu verstehen. Warum kann in diesem Beispiel f()der Wert von x, wie in ihm wahrgenommen main(), aber nicht der Wert von ngeändert werden?

def f(n, x):
    n = 2
    x.append(4)
    print('In f():', n, x)

def main():
    n = 1
    x = [0,1,2,3]
    print('Before:', n, x)
    f(n, x)
    print('After: ', n, x)

main()

Ausgabe:

Before: 1 [0, 1, 2, 3]
In f(): 2 [0, 1, 2, 3, 4]
After:  1 [0, 1, 2, 3, 4]

Antworten:


209

Einige Antworten enthalten das Wort "Kopie" in einem Kontext eines Funktionsaufrufs. Ich finde es verwirrend.

Python kopiert niemals Objekte, die Sie während eines Funktionsaufrufs übergeben .

Funktionsparameter sind Namen . Wenn Sie eine Funktion aufrufen, bindet Python diese Parameter an alle Objekte, die Sie übergeben (über Namen in einem Aufruferbereich).

Objekte können veränderlich (wie Listen) oder unveränderlich (wie Ganzzahlen, Zeichenfolgen in Python) sein. Veränderbares Objekt, das Sie ändern können. Sie können einen Namen nicht ändern, sondern nur an ein anderes Objekt binden.

In Ihrem Beispiel geht es nicht um Bereiche oder Namespaces , sondern um das Benennen und Binden sowie die Veränderbarkeit eines Objekts in Python.

def f(n, x): # these `n`, `x` have nothing to do with `n` and `x` from main()
    n = 2    # put `n` label on `2` balloon
    x.append(4) # call `append` method of whatever object `x` is referring to.
    print('In f():', n, x)
    x = []   # put `x` label on `[]` ballon
    # x = [] has no effect on the original list that is passed into the function

Hier sind schöne Bilder zum Unterschied zwischen Variablen in anderen Sprachen und Namen in Python .


3
Dieser Artikel hat mir geholfen, das Problem besser zu verstehen und schlägt eine Problemumgehung und einige erweiterte Verwendungszwecke vor: Standardparameterwerte in Python
Gfy

@Gfy, ich habe schon ähnliche Beispiele gesehen, aber für mich beschreibt es keine reale Situation. Wenn Sie etwas ändern, das übergeben wurde, ist es nicht sinnvoll, eine Standardeinstellung festzulegen.
Mark Ransom

@ MarkRansom, ich denke, es ist sinnvoll, wenn Sie ein optionales Ausgabeziel wie in: angeben möchten def foo(x, l=None): l=l or []; l.append(x**2); return l[-1].
Janusz Lenar

In der letzten Zeile von Sebastians Code stand "# das Obige hat keine Auswirkung auf die ursprüngliche Liste". Aber meiner Meinung nach hat es nur keine Auswirkung auf "n", sondern hat das "x" in der main () - Funktion geändert. Hab ich recht?
user17670

1
@ user17670: x = []in f()hat keine Auswirkung auf die Liste xin der Hauptfunktion. Ich habe den Kommentar aktualisiert, um ihn genauer zu gestalten.
JFS

15

Sie haben bereits eine Reihe von Antworten, und ich stimme JF Sebastian weitgehend zu, aber Sie finden dies möglicherweise als Abkürzung nützlich:

Jedes Mal varname =, wenn Sie sehen , erstellen Sie eine neue Namensbindung im Funktionsumfang. Welcher Wert zuvor varnamegebunden war, geht in diesem Bereich verloren .

Jedes Mal, wenn Sie sehen, dass varname.foo()Sie eine Methode aufrufen varname. Die Methode kann den Variablennamen ändern (z list.append. B. ). varname(oder vielmehr das Objekt, das benannt wird varname) kann in mehr als einem Bereich vorhanden sein, und da es sich um dasselbe Objekt handelt, sind alle Änderungen in allen Bereichen sichtbar.

[Beachten Sie, dass das globalSchlüsselwort eine Ausnahme zum ersten Fall erstellt]


13

fändert den Wert von nicht wirklich x(was immer der gleiche Verweis auf eine Instanz einer Liste ist). Vielmehr ändert es den Inhalt dieser Liste.

In beiden Fällen wird eine Kopie einer Referenz an die Funktion übergeben. Innerhalb der Funktion,

  • nwird ein neuer Wert zugewiesen. Nur die Referenz innerhalb der Funktion wird geändert, nicht die außerhalb.
  • xwird kein neuer Wert zugewiesen: Weder die Referenz innerhalb noch außerhalb der Funktion wird geändert. Stattdessen wird xder Wert von geändert.

Da sich sowohl die xInnenseite als auch die Außenseite auf denselben Wert beziehen, sehen beide die Änderung. Im Gegensatz ndazu beziehen sich das Innere der Funktion und das Äußere auf unterschiedliche Werte, nachdem nsie innerhalb der Funktion neu zugewiesen wurden.


8
"Kopie" ist irreführend. Python hat keine Variablen wie C. Alle Namen in Python sind Referenzen. Sie können den Namen nicht ändern, Sie können ihn nur an ein anderes Objekt binden, das ist alles. Es ist nur sinnvoll, über veränderbare und unveränderliche Objekte in Python zu sprechen, nicht über Namen.
JFS

1
@JF Sebastian: Deine Aussage ist bestenfalls irreführend. Es ist nicht sinnvoll, sich Zahlen als Referenzen vorzustellen.
Pitarou

9
@dysfunctor: Zahlen sind Verweise auf unveränderliche Objekte. Wenn Sie sie lieber anders sehen möchten, müssen Sie eine Reihe seltsamer Sonderfälle erklären. Wenn Sie sie als unveränderlich betrachten, gibt es keine Sonderfälle.
S.Lott

@ S.Lott: Unabhängig davon, was unter der Haube vor sich geht, hat Guido van Rossum große Anstrengungen unternommen, um Python so zu gestalten, dass der Programmierer Zahlen als nur ... Zahlen betrachten kann.
Pitarou

1
@JF, die Referenz wird kopiert.
Hablabit

7

Ich werde Variablen umbenennen, um Verwirrung zu vermeiden. n -> nf oder nmain . x -> xf oder xmain :

def f(nf, xf):
    nf = 2
    xf.append(4)
    print 'In f():', nf, xf

def main():
    nmain = 1
    xmain = [0,1,2,3]
    print 'Before:', nmain, xmain
    f(nmain, xmain)
    print 'After: ', nmain, xmain

main()

Wenn Sie die Funktion f aufrufen , erstellt die Python-Laufzeit eine Kopie von xmain und weist sie xf zu . In ähnlicher Weise weist sie nf eine Kopie von nmain zu .

Im Fall von n ist der kopierte Wert 1.

Im Fall von x ist der kopierte Wert nicht die Literalliste [0, 1, 2, 3] . Es ist ein Verweis auf diese Liste. xf und xmain zeigen auf dieselbe Liste. Wenn Sie also xf ändern, ändern Sie auch xmain .

Wenn Sie jedoch etwas schreiben würden wie:

    xf = ["foo", "bar"]
    xf.append(4)

Sie würden feststellen, dass sich xmain nicht geändert hat. Dies liegt daran, dass Sie in der Zeile xf = ["foo", "bar"] xf so geändert haben, dass es auf eine neue Liste verweist . Alle Änderungen, die Sie an dieser neuen Liste vornehmen, haben keine Auswirkungen auf die Liste, auf die xmain noch verweist.

Hoffentlich hilft das. :-)


2
"Im Fall von n der Wert, der kopiert wird ..." - Dies ist falsch, hier wird nicht kopiert (es sei denn, Sie zählen Referenzen). Stattdessen verwendet Python 'Namen', die auf die tatsächlichen Objekte verweisen. nf und xf zeigen auf nmain und xmain, bis nf = 2der Name nfgeändert wird, um auf zu zeigen 2. Zahlen sind unveränderlich, Listen sind veränderlich.
Casey Kuball

2

Das liegt daran, dass eine Liste ein veränderbares Objekt ist. Sie setzen x nicht auf den Wert [0,1,2,3], sondern definieren eine Beschriftung für das Objekt [0,1,2,3].

Sie sollten Ihre Funktion f () folgendermaßen deklarieren:

def f(n, x=None):
    if x is None:
        x = []
    ...

3
Es hat nichts mit Veränderlichkeit zu tun. Wenn Sie dies x = x + [4]stattdessen tun würden x.append(4), würden Sie auch beim Anrufer keine Änderung sehen, obwohl eine Liste veränderbar ist. Es hat damit zu tun, ob es tatsächlich mutiert ist.
glglgl

1
OTOH, wenn Sie das tun , x += [4]dann xmutiert ist, so wie das, was mit geschieht x.append(4), so wird der Anrufer die Änderung sehen.
PM 2Ring

2

n ist ein int (unveränderlich), und eine Kopie wird an die Funktion übergeben. In der Funktion ändern Sie also die Kopie.

X ist eine Liste (veränderbar), und eine Kopie des Zeigers wird an die Funktion übergeben, sodass x.append (4) den Inhalt der Liste ändert. Wenn Sie jedoch in Ihrer Funktion x = [0,1,2,3,4] gesagt haben, würden Sie den Inhalt von x in main () nicht ändern.


3
Beachten Sie die Formulierung "Kopie des Zeigers". Beide Orte erhalten Verweise auf die Objekte. n ist eine Referenz auf ein unveränderliches Objekt; x ist eine Referenz auf ein veränderliches Objekt.
S.Lott

2

Wenn die Funktionen mit völlig unterschiedlichen Variablen neu geschrieben werden und wir sie mit id aufrufen , wird der Punkt gut veranschaulicht. Ich habe das zuerst nicht verstanden und den Beitrag von jfs mit der großartigen Erklärung gelesen , also habe ich versucht, mich selbst zu verstehen / zu überzeugen:

def f(y, z):
    y = 2
    z.append(4)
    print ('In f():             ', id(y), id(z))

def main():
    n = 1
    x = [0,1,2,3]
    print ('Before in main:', n, x,id(n),id(x))
    f(n, x)
    print ('After in main:', n, x,id(n),id(x))

main()
Before in main: 1 [0, 1, 2, 3]   94635800628352 139808499830024
In f():                          94635800628384 139808499830024
After in main: 1 [0, 1, 2, 3, 4] 94635800628352 139808499830024

z und x haben die gleiche ID. Nur verschiedene Tags für dieselbe zugrunde liegende Struktur wie im Artikel angegeben.


0

Python ist eine reine Pass-by-Value-Sprache, wenn Sie richtig darüber nachdenken. Eine Python-Variable speichert den Speicherort eines Objekts im Speicher. Die Python-Variable speichert das Objekt selbst nicht. Wenn Sie eine Variable an eine Funktion übergeben, übergeben Sie eine Kopie der Adresse des Objekts, auf das die Variable zeigt.

Vergleichen Sie diese beiden Funktionen

def foo(x):
    x[0] = 5

def goo(x):
    x = []

Nun, wenn Sie in die Shell eingeben

>>> cow = [3,4,5]
>>> foo(cow)
>>> cow
[5,4,5]

Vergleichen Sie dies mit goo.

>>> cow = [3,4,5]
>>> goo(cow)
>>> goo
[3,4,5]

Im ersten Fall übergeben wir eine Kopie der Adresse der Kuh an foo und foo hat den Status des dort befindlichen Objekts geändert. Das Objekt wird geändert.

Im zweiten Fall übergeben Sie eine Kopie der Adresse der Kuh an goo. Dann ändert goo diese Kopie. Wirkung: keine.

Ich nenne das das Prinzip des rosa Hauses . Wenn Sie eine Kopie Ihrer Adresse erstellen und einem Maler sagen, dass er das Haus an dieser Adresse rosa streichen soll, erhalten Sie ein rosa Haus. Wenn Sie dem Maler eine Kopie Ihrer Adresse geben und ihn auffordern, diese in eine neue Adresse umzuwandeln, ändert sich die Adresse Ihres Hauses nicht.

Die Erklärung beseitigt viel Verwirrung. Python übergibt die Adressvariablen, die nach Wert gespeichert sind.


Ein reiner Pass-by-Pointer-Wert unterscheidet sich nicht sehr von einem Pass-by-Reference-Wert, wenn Sie richtig darüber nachdenken ...
Galinette

Schau dir goo an. Wären Sie ein reiner Hinweis, hätte dies sein Argument geändert. Nein, Python ist keine reine Referenzsprache. Es werden Referenzen nach Wert übergeben.
Ncmathsadist

0

Python wird nach Referenzwert kopiert. Ein Objekt belegt ein Feld im Speicher, und diesem Objekt ist eine Referenz zugeordnet, die jedoch selbst ein Feld im Speicher belegt. Und Name / Wert ist einer Referenz zugeordnet. In der Python-Funktion wird immer der Wert der Referenz kopiert, sodass in Ihrem Code n als neuer Name kopiert wird. Wenn Sie diesen zuweisen, hat es einen neuen Platz im Aufruferstapel. Für die Liste wurde der Name ebenfalls kopiert, bezieht sich jedoch auf denselben Speicher (da Sie der Liste niemals einen neuen Wert zuweisen). Das ist eine Magie in Python!


0

Mein allgemeines Verständnis ist, dass jede Objektvariable (wie z. B. eine Liste oder ein Diktat) durch ihre Funktionen geändert werden kann. Ich glaube, Sie können den Parameter nicht neu zuweisen, dh ihn innerhalb einer aufrufbaren Funktion als Referenz zuweisen.

Das stimmt mit vielen anderen Sprachen überein.

Führen Sie das folgende kurze Skript aus, um zu sehen, wie es funktioniert:

def func1(x, l1):
    x = 5
    l1.append("nonsense")

y = 10
list1 = ["meaning"]
func1(y, list1)
print(y)
print(list1)

-3

Ich hatte meine Antwort tonnenweise geändert und festgestellt, dass ich nichts zu sagen habe, Python hatte sich bereits erklärt.

a = 'string'
a.replace('t', '_')
print(a)
>>> 'string'

a = a.replace('t', '_')
print(a)
>>> 's_ring'

b = 100
b + 1
print(b)
>>> 100

b = b + 1
print(b)
>>> 101

def test_id(arg):
    c = id(arg)
    arg = 123
    d = id(arg)
    return

a = 'test ids'
b = id(a)
test_id(a)
e = id(a)

# b = c  = e != d
# this function do change original value
del change_like_mutable(arg):
    arg.append(1)
    arg.insert(0, 9)
    arg.remove(2)
    return

test_1 = [1, 2, 3]
change_like_mutable(test_1)



# this function doesn't 
def wont_change_like_str(arg):
     arg = [1, 2, 3]
     return


test_2 = [1, 1, 1]
wont_change_like_str(test_2)
print("Doesn't change like a imutable", test_2)

Dieser Teufel ist nicht die Referenz / Wert / veränderlich oder nicht / Instanz, Namensraum oder Variable / Liste oder str, ES IST DER SYNTAX, GLEICHES ZEICHEN.

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.