我试图理解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]

请参见:如何通过引用传递变量?


当前回答

你已经得到了一些答案,我基本上同意J.F.塞巴斯蒂安的观点,但你可能会发现这是一条有用的捷径:

任何时候看到varname =,都是在函数的作用域内创建了一个新的名称绑定。无论之前varname绑定到什么值,都将在此范围内丢失。

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

[注意,global关键字对第一个情况创建了一个异常]

其他回答

正如jouell所说。这是一个什么指向什么的问题,我想补充的是,这也是一个=做什么和.append方法做什么之间的区别的问题。

When you define n and x in main, you tell them to point at 2 objects, namely 1 and [1,2,3]. That is what = does : it tells what your variable should point to. When you call the function f(n,x), you tell two new local variables nf and xf to point at the same two objects as n and x. When you use "something"="anything_new", you change what "something" points to. When you use .append, you change the object itself. Somehow, even though you gave them the same names, n in the main() and the n in f() are not the same entity, they only originally point to the same object (same goes for x actually). A change to what one of them points to won't affect the other. However, if you instead make a change to the object itself, that will affect both variables as they both point to this same, now modified, object.

让我们在不定义新函数的情况下说明.append方法和=方法之间的区别:

比较

    m = [1,2,3]
    n = m   # this tells n to point at the same object as m does at the moment
    m = [1,2,3,4] # writing m = m + [4] would also do the same
    print('n = ', n,'m = ',m)

to

    m = [1,2,3]
    n = m
    m.append(4)
    print('n = ', n,'m = ',m)

在第一个代码中,它将打印n = [1,2,3] m =[1,2,3,4],因为在第三行,你没有改变对象[1,2,3],而是你告诉m指向一个新的,不同的对象(使用'='),而n仍然指向原始对象。

在第二段代码中,它将输出n = [1,2,3,4] m =[1,2,3,4]。这是因为在整个代码中m和n仍然指向同一个对象,但是您使用.append方法修改了对象本身(m所指向的对象)…注意,不管你在第三行写m.p append(4)还是n.p append(4),第二段代码的结果都是一样的。

一旦你理解了这一点,剩下的唯一困惑就是真正理解,正如我所说,f()函数中的n和x与main()中的n和x并不相同,它们只是在调用f()时最初指向同一个对象。

我将重命名变量以减少混乱。N -> nf或nmain。X -> xf或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()

当你调用函数f时,Python运行时生成一个xmain的副本并将其赋值给xf,类似地,将nmain的副本赋值给nf。

在n的情况下,复制的值是1。

对于x,复制的值不是文本列表[0,1,2,3]。它是对那个列表的参考。Xf和xmain都指向同一个列表,所以当你修改Xf时,你也在修改xmain。

然而,如果你要写这样的东西:

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

您会发现xmain没有改变。这是因为,在行xf = ["foo", "bar"]中,您更改了xf以指向一个新的列表。对这个新列表所做的任何更改都不会对xmain仍然指向的列表产生影响。

希望这能有所帮助。: -)

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

对比这两个函数

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

def goo(x):
    x = []

现在,当你输入外壳的时候

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

将其与goo进行比较。

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

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

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

我称之为粉红房子原则。如果你把你的地址复印一份,并告诉a 如果油漆工把那个地址的房子漆成粉红色,你就会得到一座粉红色的房子。 如果你给油漆工一份你的地址复印件,让他把它改成一个新地址, 你家的地址不变。

这种解释消除了许多困惑。Python将地址变量按值存储。

有些答案在函数调用的上下文中包含“复制”一词。我觉得很困惑。

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中的名称之间的区别。

如果用完全不同的变量重写函数,并且我们对它们调用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。就像文章所说的那样,不同的标签对应相同的底层结构。