为什么函数可以修改调用者认为的某些自变量,而不能修改其他自变量?

问题:为什么函数可以修改调用者认为的某些自变量,而不能修改其他自变量?

我正在尝试了解Python的可变范围方法。在此示例中,为什么f()能够更改的值(x如在其中看到的)main(),但不能更改的值n

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()

输出:

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

I’m trying to understand Python’s approach to variable scope. In this example, why is f() able to alter the value of x, as perceived within main(), but not the value of n?

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()

Output:

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

回答 0

一些答案在函数调用的上下文中包含单词“ copy”。我感到困惑。

Python不复制对象的函数调用中传递

功能参数是名称。当您调用函数时,Python会将这些参数绑定到您传递的任何对象上(通过调用方作用域中的名称)。

对象可以是可变的(如列表)或不可变的(如Python中的整数,字符串)。您可以更改可变对象。您不能更改名称,只需将其绑定到另一个对象即可。

您的示例与作用域或命名空间无关,与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

这是其他语言中的变量与Python中的名称之间的区别的好照片。

Some answers contain the word “copy” in a context of a function call. I find it confusing.

Python doesn’t copy objects you pass during a function call ever.

Function parameters are names. When you call a function Python binds these parameters to whatever objects you pass (via names in a caller scope).

Objects can be mutable (like lists) or immutable (like integers, strings in Python). Mutable object you can change. You can’t change a name, you just can bind it to another object.

Your example is not about scopes or namespaces, it is about naming and binding and mutability of an object 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

Here are nice pictures on the difference between variables in other languages and names in Python.


回答 1

您已经有了许多答案,并且我在很大程度上同意JF Sebastian的观点,但是您可能会觉得这很实用:

每次看到时varname =,您都在函数范围内创建新的名称绑定。在此范围内,varname丢失的绑定值将丢失。

每当您看到varname.foo()自己在上调用方法时varname。该方法可以更改varname(例如list.append)。 varname(或更确切地说,varname命名)可能存在于多个作用域中,并且由于它是同一对象,因此所有更改都将在所有作用域中可见。

[请注意,global关键字创建了第一种情况的exceptions]

You’ve got a number of answers already, and I broadly agree with J.F. Sebastian, but you might find this useful as a shortcut:

Any time you see varname =, you’re creating a new name binding within the function’s scope. Whatever value varname was bound to before is lost within this scope.

Any time you see varname.foo() you’re calling a method on varname. The method may alter varname (e.g. list.append). varname (or, rather, the object that varname names) may exist in more than one scope, and since it’s the same object, any changes will be visible in all scopes.

[note that the global keyword creates an exception to the first case]


回答 2

f实际上并不会更改x(始终与列表实例的引用相同)值。而是,它更改了此列表的内容

在这两种情况下,引用副本都传递给该函数。在函数内部,

  • n被分配一个新值。仅修改函数内部的引用,而不修改函数外部的引用。
  • x不会被分配新值:函数内部和外部的引用均未修改。而是会修改x

由于x函数内部和外部都引用相同的值,因此请参见修改。相比之下,n在函数内部重新分配后,函数内部和外部引用不同的n

f doesn’t actually alter the value of x (which is always the same reference to an instance of a list). Rather, it alters the contents of this list.

In both cases, a copy of a reference is passed to the function. Inside the function,

  • n gets assigned a new value. Only the reference inside the function is modified, not the one outside it.
  • x does not get assigned a new value: neither the reference inside nor outside the function are modified. Instead, x’s value is modified.

Since both the x inside the function and outside it refer to the same value, both see the modification. By contrast, the n inside the function and outside it refer to different values after n was reassigned inside the function.


回答 3

我将重命名变量以减少混乱。 n- > nf nmainx- > xfxmain

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()

当您调用函数f,Python运行时将复制xmain并将其分配给xf,类似地将nmain的副本分配 给nf

n的情况下,复制的值为1。

x的情况下,复制的值不是文字列表[0,1,2,3]。它是对该列表的引用xfxmain指向相同的列表,因此,当您修改xf时,您也在修改xmain

但是,如果您要编写类似以下内容的内容:

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

您会发现xmain尚未更改。这是因为在xf = [“ foo”,“ bar”]行中您已更改xf以指向列表。您对此新列表所做的任何更改都不会影响xmain仍指向的列表。

希望有帮助。:-)

I will rename variables to reduce confusion. n -> nf or nmain. x -> xf or 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()

When you call the function f, the Python runtime makes a copy of xmain and assigns it to xf, and similarly assigns a copy of nmain to nf.

In the case of n, the value that is copied is 1.

In the case of x the value that is copied is not the literal list [0, 1, 2, 3]. It is a reference to that list. xf and xmain are pointing at the same list, so when you modify xf you are also modifying xmain.

If, however, you were to write something like:

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

you would find that xmain has not changed. This is because, in the line xf = [“foo”, “bar”] you have change xf to point to a new list. Any changes you make to this new list will have no effects on the list that xmain still points to.

Hope that helps. :-)


回答 4

这是因为列表是可变对象。您没有将x设置为[0,1,2,3]的值,而是为对象[0,1,2,3]定义了标签。

您应该这样声明函数f():

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

It´s because a list is a mutable object. You´re not setting x to the value of [0,1,2,3], you´re defining a label to the object [0,1,2,3].

You should declare your function f() like this:

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

回答 5

n是一个int(不可变),并且副本将传递给该函数,因此在函数中您将更改副本。

X是一个列表(可变的),并且该函数传递了指针的副本,因此x.append(4)更改了列表的内容。但是,您在函数中说x = [0,1,2,3,4],则不会在main()中更改x的内容。

n is an int (immutable), and a copy is passed to the function, so in the function you are changing the copy.

X is a list (mutable), and a copy of the pointer is passed o the function so x.append(4) changes the contents of the list. However, you you said x = [0,1,2,3,4] in your function, you would not change the contents of x in main().


回答 6

如果用完全不同的变量重写函数,然后在它们上调用id,则可以很好地说明这一点。我一开始并没有得到这个,并以出色的解释阅读了jfs的帖子,所以我试图理解/说服自己:

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和x具有相同的ID。与本文所述相同的基础结构只有不同的标记。

If the functions are re-written with completely different variables and we call id on them, it then illustrates the point well. I didn’t get this at first and read jfs’ post with the great explanation, so I tried to understand/convince myself:

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 and x have the same id. Just different tags for the same underlying structure as the article says.


回答 7

如果您以正确的方式考虑Python,那么它就是一种纯粹的按值传递语言。python变量将对象的位置存储在内存中。Python变量不存储对象本身。当您将变量传递给函数时,您正在传递副本变量所指向的对象地址的。

对比这两个功能

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

def goo(x):
    x = []

现在,当您输入外壳时

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

与此进行比较。

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

在第一种情况下,我们将Cow的地址副本传递给foo,而foo修改了驻留在此的对象的状态。对象被修改。

在第二种情况下,您将cow的地址副本传递给了goo。然后goo继续更改该副本。效果:无。

我称之为粉红色房屋原则。如果您复制住址并告诉画家将该地址的房子粉刷成粉红色,那么您将得到一间粉红色的房子。如果您给画家一个地址的副本,并告诉他将其更改为新地址,则您房屋的地址不变。

这种解释消除了很多混乱。Python通过值传递地址变量存储。

Python is a pure pass-by-value language if you think about it the right way. A python variable stores the location of an object in memory. The Python variable does not store the object itself. When you pass a variable to a function, you are passing a copy of the address of the object being pointed to by the variable.

Contrasst these two functions

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

def goo(x):
    x = []

Now, when you type into the shell

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

Compare this to goo.

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

In the first case, we pass a copy the address of cow to foo and foo modified the state of the object residing there. The object gets modified.

In the second case you pass a copy of the address of cow to goo. Then goo proceeds to change that copy. Effect: none.

I call this the pink house principle. If you make a copy of your address and tell a painter to paint the house at that address pink, you will wind up with a pink house. If you give the painter a copy of your address and tell him to change it to a new address, the address of your house does not change.

The explanation eliminates a lot of confusion. Python passes the addresses variables store by value.


回答 8

Python是按引用值复制的。一个对象占用内存中的一个字段,并且引用与该对象相关联,但其自身占用内存中的一个字段。并且名称/值与引用相关联。在python函数中,它总是复制引用的值,因此在您的代码中,n被复制为一个新名称,当您分配该名称时,它在调用者堆栈中具有一个新空间。但是对于列表,该名称也被复制,但是它引用相同的内存(因为您从未为列表分配新值)。那是python中的魔力!

Python is copy by value of reference. An object occupies a field in memory, and a reference is associated with that object, but itself occupies a field in memory. And name/value is associated with a reference. In python function, it always copy the value of the reference, so in your code, n is copied to be a new name, when you assign that, it has a new space in caller stack. But for the list, the name also got copied, but it refer to the same memory(since you never assign the list a new value). That is a magic in python!


回答 9

我的一般理解是,任何对象变量(例如列表或字典等)都可以通过其功能进行修改。我认为您无法执行的操作是重新分配参数-即,在可调用函数中通过引用进行分配。

这与许多其他语言一致。

运行以下简短脚本以查看其工作方式:

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

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

My general understanding is that any object variable (such as a list or a dict, among others) can be modified through its functions. What I believe you are not able to do is reassign the parameter – i.e., assign it by reference within a callable function.

That is consistent with many other languages.

Run the following short script to see how it works:

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

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

回答 10

我已经无数次修改了答案,并意识到我不必说什么,python已经解释了自己。

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)

这个魔鬼不是引用/值/可变或非实例/命名空间或变量/列表或str,而是语法,等号。

I had modified my answer tons of times and realized i don’t have to say anything, python had explained itself already.

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)

This devil is not the reference / value / mutable or not / instance, name space or variable / list or str, IT IS THE SYNTAX, EQUAL SIGN.