Python中默认函数参数的生命周期

7

我刚开始学习Python,遇到了默认参数的概念。

Python文档中提到,函数的默认参数值仅在第一次遇到def语句时计算一次。这导致不可变和可变默认函数参数的值之间存在很大差异。

>>> def func(a,L=[]):
      L.append(a)
      return L
>>> print(func(1))
[1]
>>> print(func(2))
[1, 2]

这里的可变函数参数L保留了最后分配的值(因为默认值不像在C中一样在函数调用期间计算)。

在Python中,函数默认参数值的生命周期是整个程序的生命周期(就像C语言中的静态变量一样)吗?

编辑:

>>> Lt = ['a','b']
>>> print(func(3,Lt))
['a', 'b', 3]
>>> print(func(4))
[1, 2, 4]

在函数调用期间,例如func(3,Lt)L的默认值被保留,不会被Lt覆盖。

那么默认参数有两个内存吗?一个是实际默认值(具有程序范围),另一个是传递给它的对象时的内存(具有函数调用的作用域)?


我认为关键点在于def是Python中的一个语句,会被执行。默认参数在函数创建时被计算,而不是在调用函数时计算。 - Steven Rumbalski
1
在你的编辑中,第一次调用函数时,你传递了一个新列表到函数中,因此默认参数在那一刻被忽略了。那个列表(Lt)在函数返回后不再被函数引用。 - Martijn Pieters
1
如果你在调用 func() 时没有传入第二个参数,那么本地名称 L 将引用默认值列表,如果你传入一个列表 Lt,那么 L 将引用该列表。 在这两种情况下,当函数返回时,该引用将被清除,L 将不再存在。然而,它所引用的值仍然存在,因为它有其他名称引用它。 - Martijn Pieters
1
CPython使用引用计数;每个值都计算有多少引用指向它。一旦计数达到0,该值就会被销毁。有一个垃圾回收程序可以发现循环引用并打破它们以帮助此过程。 - Martijn Pieters
2个回答

14

由于参数是函数对象的属性,它通常与函数具有相同的生命周期。通常情况下,函数从其模块被加载的那一刻开始存在,直到解释器退出。

然而,Python 函数是一等对象,您可以在早期删除对该函数的所有引用(动态地)。垃圾回收器随后可能会回收该函数并回收默认参数:

>>> def foo(bar, spam=[]):
...     spam.append(bar)
...     print(spam)
... 
>>> foo
<function foo at 0x1088b9d70>
>>> foo('Monty')
['Monty']
>>> foo('Python')
['Monty', 'Python']
>>> foo.func_defaults
(['Monty', 'Python'],)
>>> del foo
>>> foo
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'foo' is not defined

请注意您可以直接访问func_defaults属性(在Python 3中为__defaults__),它是可写的,因此您可以通过重新赋值该属性来清除默认值。


1
默认值的生命周期难道不能比函数的生命周期更短吗?foo.func_defaults = ([],) - Steven Rumbalski
@StevenRumbalski:确实,该属性是可写的。 - Martijn Pieters
@MartijnPieters 所以默认参数会在函数引用存在的时候一直存在于内存中吗? '>>> Lt = ['a','b']
print(func(3,Lt)) ['a', 'b', 3] print(func(4)) [1, 2, 4]'
所以默认参数有两个内存?一个是实际的默认值,另一个是当对象传递给它时的内存?
- ParokshaX
@SamEmrys:不,这就是重点;它是一个默认值,并且是直接操作的默认值。列表是可变的,它们是对其他值的引用序列。您可以从该序列中添加和删除引用。因此,函数默认指向一个列表,并且该列表被直接操作。这就是更改在调用函数之间保持不变的原因。 - Martijn Pieters
@MartijnPieters 是的,我明白了...函数默认参数存储在func_defaults属性中(在Python 3中为__defaults__),以列表形式呈现。 - ParokshaX
@SamEmrys:是元组,没错,你懂了。 :-) - Martijn Pieters

3
它的寿命至少与函数对象相同(除非出现异常情况)。因此,如果加载它的模块被所有其他模块卸载,并且没有其他引用,则函数对象及其成员可能会被销毁。
由于Python是垃圾回收的,从实际意义上讲,您不需要担心这个问题。如果对象逃逸并且程序的其他部分有引用,它将一直存在。
如果您的观点是希望依赖该对象存在并且不被重置,那么是的,您可以这样做,除非有一些异常情况卸载了模块,或者您有一些代码将默认值存储在函数对象的变量中进行赋值。

任何具有对默认值的引用的活动对象都将保持该值的存活状态,即使函数已卸载。因此,使用问题中的定义,X=func('q')将为X提供对列表的引用,该列表将包含由其他函数调用附加的'q'和任何其他内容。删除func(),X将保持列表的存活状态。 - Jerry B
@JerryB 是的,这并不与我所说的相矛盾。 - Marcin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接