不可变默认参数值的Python行为

时间:2014-04-30 20:00:58

标签: python default immutability

>>> def a():
...     print "a executed"
...     return []
... 
>>>            
>>> def b(x=a()):
...     x.append(5)
...     print x
... 
a executed
>>> b()
[5]
>>> b()
[5, 5]

首次定义函数b时,x绑定到空列表对象。每次调用b时都会修改空列表对象,因为b绑定到对象。

我不知道的是当不可变对象发生这种情况时:

>>> def a():
...     print "a executed"
...     return 0
... 
>>>            
>>> def b(x=a()):
...     x = x + 2
...     print x
... 
a executed
>>> b()
2
>>> b()
2

从我的POV开始,当首次定义函数b时,x绑定到int对象0。然后,在调用b()时修改x。因此,对b()的后续调用应该将x重新绑定到2,4,6等等。为什么不发生这种情况?我显然在这里缺少一些重要的东西!

Thx:)

2 个答案:

答案 0 :(得分:4)

当您执行x =时,您并未修改x引用的对象,您只是将引用x更改为指向其他对象,这种情况下,另一个int。在这种情况下,无论x是否指向不可变对象,它的事件都无关紧要。如果您使用列表进行x = x + [5],它也将保持不变。请注意区别:

def b1(x = []):
    x = x + [5]
    print(x)

def b2(x = []):
    x.append(5)
    print(x)

print("b1:")
b1()
print("b1:")
b1()

print("b2:")
b2()
print("b2:")
b2()

给出:

b1:
[5]
b1:
[5]
b2:
[5]
b2:
[5, 5]

当执行该功能时,您正在处理使用默认值初始化或由调用者提供的本地变量x。因此,反弹的是局部变量x,而不是参数的默认值。

您可能还想了解正式实际参数之间的区别。它与此问题只有轻微关系,但可以帮助您更好地理解这一点。可以找到示例解释here

答案 1 :(得分:2)

小心,两者之间存在巨大差异:

x.append(5)

x = x + 1

即,第一个改变x引用的对象,而第二个创建一个新对象,该对象是x + 1的结果,并将其重新绑定到名称x

当然,这有点过分简化 - 例如如果您使用过+=怎么办... 它实际上取决于__add____iadd__的定义方式,但这应该得到重点...


为了更深入,您可以将函数视为对象的实例。它有一些特殊的属性,你甚至可以查看:

>>> def foo(x = lst): pass
... 
>>> foo.func_defaults
([],)
>>> foo.func_defaults[0] is lst
True

定义函数后,func_defaults 1 将被设置。每次调用该函数时,python都会查看默认值和调用中存在的内容,并确定哪些默认值传递给函数以及哪些函数已经提供。需要注意的是,这就是为什么当你在第一种情况下附加到列表时,更改仍然存在 - 你实际上也在改变func_defaults中的值。在您使用x = x + 1的第二种情况下,您实际上并没有做任何改变func_defaults的事情 - 您只是在创建新的东西并将其放入函数的命名空间。

1 该属性在python3.x中仅为__defaults__