传入函数的Python字典在该函数中表现得像全局变量而非局部变量
我对下面的行为感到很困惑。案例1、3和4的表现符合我的预期,但案例2却不一样。为什么案例2允许这个函数全局改变字典中的值,即使这个字典并没有被函数返回?我使用函数的一个主要原因是想把函数里的东西和其他代码隔离开,但如果我在函数内部使用相同的变量名,这似乎就做不到。我原本以为在函数中明确定义的东西都是局部的,但如果字典是在函数中定义并作为输入传入的,这似乎就不成立了。
案例 1
>>> def testfun1(a):
... a=2
...
>>> a=0
>>> testfun1(a)
>>> a
0
案例 2
>>> def testfun2(b):
... b['test']=2
...
>>> b={}
>>> testfun2(b)
>>> b
{'test': 2}
案例 3
>>> def testfun3():
... c=2
...
>>> c=0
>>> testfun3()
>>> c
0
案例 4
(这个问题解释了:全局字典不需要使用global关键字就能修改它们?)
>>> def testfun4():
... d['test']=10
...
>>> d={}
>>> testfun4()
>>> d
{'test': 10}
5 个回答
当你把一个基本的对象,比如整数或字符串,传给一个函数时,如果你在函数里面改变了它,外面的那个对象不会有任何变化。这是因为在处理基本对象时,Python是通过值传递的。
但是,如果你把一个字典或列表传给函数,它们是通过引用传递的。这就意味着你在函数里面做的改变会影响到外面对应的对象,就像你看到的那样。
补充:另外,值传递和引用传递之间是有区别的:值传递时,会在函数内部创建一个对象的“副本”来使用;而引用传递时,传递的是同一个对象的引用,函数内部的修改在外面是可见的。根据定义,Python会通过值传递不可变对象,通过引用传递可变对象。
为了支持@Casey Kuball所说的,每个Python中的对象都是通过引用传递的。也就是说,每个函数接收到的是你传入的实际对象的一个引用。修改这些对象是否成功,取决于它们是否是可变的数据类型。
简单来说,可变对象,比如字典、集合和列表,是通过引用传递的。而不可变对象,比如int
(整数)、str
(字符串)、tuple
(元组),则是通过值传递的。
你还应该注意,有些情况下,可变对象在函数内部被覆盖,这样就失去了对传入函数的实际对象的引用。
>>> def testfun(b):
... b = b or {} # Creates a new object if b is false
... b['test'] = 2
...
>>> b = {}
>>> testfun(b)
>>> b
{}
Python的“参数评估策略”跟你可能熟悉的语言有点不同。它没有明确的值传递和引用传递,而是采用了共享传递的方式。简单来说,你传递的总是对象本身,而对象是否可以被修改取决于它的可变性。列表和字典是可变对象,而数字、字符串和元组则不是。
你传递给函数的是字典本身,而不是它的副本。因此,当你修改这个字典时,实际上也在修改原来的字典。
为了避免这种情况,你应该在调用函数之前先复制字典,或者在函数内部进行复制(将字典传递给dict
函数就可以,比如testfun4(dict(d))
,并将函数定义为def testfun4(d):
)。