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

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


当前回答

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

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

其他回答

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

X是一个列表(可变),指针的副本被传递给函数,因此x.a pend(4)改变了列表的内容。然而,你在你的函数中说x =[0,1,2,3,4],你不会在main()中改变x的内容。

这是因为列表是一个可变对象。你不是将x设置为[0,1,2,3]的值,你是在为对象[0,1,2,3]定义一个标签。

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

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

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

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

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

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

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

F实际上不会改变x的值(它总是对列表实例的相同引用)。相反,它改变了这个列表的内容。

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

N被赋一个新值。只修改函数内部的引用,而不修改函数外部的引用。 X不会被赋一个新值:函数内部和外部的引用都不会被修改。相反,x的值被修改了。

由于函数内部和外部的x都指向相同的值,所以两者都看到了修改。相比之下,函数内部和外部的n指的是在函数内部重新赋值n后的不同值。