在Python中,为什么函数可以修改调用者感觉到的一些参数,而不是其他的?
我是Python的新手,并试图理解它对variables作用域的方法。 在这个例子中,为什么f()
能够改变在main()
感知的x
的值,而不是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中的整数,string)。 可变对象,你可以改变。 您不能更改名称,只能将其绑定到另一个对象。
你的例子不是范围或命名空间 ,而是关于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
这里有其他语言的variables和Python中的名称之间的区别 。
f
实际上并不改变x
的值(它总是与列表的一个实例相同的引用)。 而是改变这个列表的内容 。
在这两种情况下, 副本都被传递给函数。 但是由于x
是对列表实例的引用,所以只复制引用,而不是列表的内容。
如果你熟悉C,下面的代码解释了Python代码的语义:
void f(int n, int* x) { n = 42; x[0] = 2; }
在这里, n
是一个int
, x
是一个int*
但都作为副本传递给函数。 但是, x
所指向的内存在调用者和被调用者的侧面是相同的。
已经有很多答案了,我大致上同意JF Sebastian的看法,但是你可能会发现这个方法是一个捷径:
每当你看到varname =
,你就在函数的作用域内创build一个新的名字绑定。 在这个范围内, varname
之前绑定的值是什么。
每当你看到varname.foo()
你就调用varname
上的方法。 该方法可能会改变varname(例如list.append
)。 varname
(或者说, varname
名称的对象)可能存在于多个作用域中,并且由于它是同一个对象,因此所有作用域中的所有更改都将可见。
[请注意, global
关键字会为第一个案例创build一个例外]
我会重命名variables以减less混淆。 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仍指向的列表产生任何影响。
希望有所帮助。 🙂
这是因为列表是一个可变的对象。 你不是把x设置为[0,1,2,3]的值,你要为对象[0,1,2,3]定义一个标签。
你应该像这样声明你的函数f()
def f(n, x=None): if x is None: x = [] ...
n是一个int(不可变),并将一个副本传递给函数,所以在函数中你正在改变副本。
X是一个列表(可变的),并且指针的一个副本被传递给函数,所以x.append(4)改变列表的内容。 不过,你在你的函数中说你x = [0,1,2,3,4],你不会改变main()中x的内容。
如果你正确地思考Python,那么Python是一种纯粹的按价值传递的语言。 一个pythonvariables存储对象在内存中的位置。 Pythonvariables不存储对象本身。 当您将一个variables传递给一个函数时,您正在传递该variables所指向的对象的地址副本 。
对比这两个function
def foo(x): x[0] = 5 def goo(x): x = []
现在,当你键入shell
>>> cow = [3,4,5] >>> foo(cow) >>> cow [5,4,5]
把这个和goo比较一下。
>>> cow = [3,4,5] >>> goo(cow) >>> goo [3,4,5]
在第一种情况下,我们将牛的地址复制到foo,并且foo修改驻留在那里的对象的状态。 对象被修改。
在第二种情况下,你把牛的地址的副本传给咕。 然后goo继续改变那个副本。 效果:无。
我称这是粉红色的房子原则 。 如果你复印一下你的地址,并告诉画家在粉红色的地址上画房子,那么你就会粉红色的房子。 如果你给画家一份你的地址的副本,并告诉他把它改成一个新的地址,你的房子的地址不会改变。
这个解释消除了很多混淆。 Python通过值传递地址variables存储。
Python是通过引用的值复制的。 一个对象在内存中占据一个字段,并且引用与该对象相关联,但是它本身占用了内存中的一个字段。 名称/值与参考相关联。 在python函数中,它总是复制引用的值,所以在你的代码中,n被复制成一个新名字,当你赋值时,它在调用者栈中有一个新的空间。 但是对于这个列表,这个名字也被复制了,但是它引用了相同的内存(因为你从来不给这个列表赋一个新的值)。 这是python的魔力!