关于参数:为什么通过python默认变量初始化变量会在对象实例化过程中保持状态?

Why does initializing a variable via a python default variable keep state across object instantiation?

我今天遇到了一个有趣的python bug,在这个bug中,反复实例化一个类似乎处于保持状态。在以后的实例化调用中,已经定义了变量。

我将问题归结为以下类/shell交互。我认识到这不是初始化类变量的最佳方法,但它肯定不应该这样做。这是一个真正的bug还是一个"特性"?D

测试:

1
2
3
4
5
6
7
8
9
10
11
12
class Tester():
        def __init__(self):
                self.mydict = self.test()

        def test(self,out={}):
                key ="key"
                for i in ['a','b','c','d']:
                        if key in out:
                                out[key] += ','+i
                        else:  
                                out[key] = i
                return out

Python提示:

1
2
3
4
5
6
7
8
9
Python 2.6.6 (r266:84292, Oct  6 2010, 00:44:09)
[GCC 4.2.1 (Apple Inc. build 5664)] on darwin
>>> import tester
>>> t = tester.Tester()
>>> print t.mydict
{'key': 'a,b,c,d'}
>>> t2 = tester.Tester()
>>> print t2.mydict
{'key': 'a,b,c,d,a,b,c,d'}


这是几乎所有的Python用户都会遇到的一个特性。主要用于缓存等,以避免重复冗长的计算(实际上是简单的记忆化),尽管我确信人们已经找到了它的其他用途。

这样做的原因是,def语句只执行一次,即在定义函数时执行一次。因此,初始值设定项值只创建一次。对于像列表或字典这样的引用类型(与不能更改的不可变类型相反),这最终会成为一个可见的、令人惊讶的陷阱,而对于值类型,则不会被注意到。

通常情况下,人们都是这样工作的:

1
2
3
4
def test(a=None):
    if a is None:
        a = {}
    # ... etc.


通常,默认方法参数不应是可变的。而是这样做:

1
2
3
def test(self, out=None):
   out = out or {}
   # other code goes here.

请参阅这些链接,了解更多关于为什么需要这样做以及为什么它是Python语言的"特性"而不是bug的详细信息。

  • "最小惊异"和可变的默认参数
  • http://effbot.org/zone/default-values.htm(http://effbot.org/zone/default-values.htm)


您正在修改方法中的函数关键字参数out的值。

这篇博文简洁地解释了这一点:

expressions in default arguments are calculated when the function is defined, not when it’s called.

函数是在创建类时定义的,不是为每个实例定义的。如果您这样修改它,问题就消失了:

1
2
3
4
5
6
7
8
9
10
def test(self,out=None):
        if out is None:
                out = {}
        key ="key"
        for i in ['a','b','c','d']:
                if key in out:
                        out[key] += ','+i
                else:  
                        out[key] = i
        return out