Python - 输入输出参数

1 投票
3 回答
1250 浏览
提问于 2025-04-17 12:41

我在《专家级Python编程》这本书里读到过一个边缘案例。看看这段代码:

def f(arg={}):
    arg['3'] = 4
    return arg

>>> print f()
{'3': 4}
>>> res = f()
>>> res['4'] = 'Still here'
>>> print f()
{'3': 4, '4': 'Still here'}

我不太明白为什么当f最后一次被调用时(在它的返回值被保存之后),它没有把参数arg赋值为空字典(因为它是没有参数调用的),而是保留了之前的引用。

书里是这么说的:“如果一个对象是在参数中创建的,那么如果函数返回这个对象,参数的引用仍然会存在。”

我明白“就是这样工作的”,但为什么会这样呢?

3 个回答

1

默认参数是在函数声明的时候创建的,也就是说每次调用这个函数 f() 时,都会使用同一个字典实例,而这个字典一开始是空的。这样解释清楚了吗?

2

你的问题是使用了一个可变的默认参数(在这个例子中是字典):

def f(arg={}):
    arg['3'] = 4
    return arg

应该改成:

def f(arg=None):
    arg = arg if arg is not None else {}
    arg['3'] = 4
    return arg

这样就会得到:

>>> print f()
{'3': 4}
>>> res = f()
>>> res['4'] = 'Still here'
>>> print f()
{'3': 4}

就像你预期的那样。

这里的问题是,默认参数是在函数第一次定义或解析的时候就被计算出来的,而不是在函数被调用的时候。这是Python解析器的一个小细节,你需要了解一下。

想了解原因,可以看看 “最小惊讶原则”和可变默认参数

2

因为默认参数只在函数被创建时计算一次(它们是函数定义的一部分,可以通过 inspect.getargspec 等方法获取)。

既然它们是函数的一部分,那么每次调用这个函数时,都会使用同一个实例的默认值。如果这个默认值是不可变的,那就没问题,但如果它是可变的,就可能会引发一些意外情况。

在类定义中也有同样的“特性”,比如有一个类的定义:

class A(object):
    foo = {}

调用

x = A() 
y = A()
x.foo['bar'] = "baz"

...会导致 y.foo['bar'] 的值为 "baz",因为 x 和 y 共享同一个 foo。这就是为什么成员初始化应该在 init 方法中进行,而不是在类体中。

撰写回答