Python函数:可选参数计算一次?

Python function: Optional argument evaluated once?

python教程4.7.1.默认参数值的状态如下:

Important warning: The default value is evaluated only once. This makes a difference
when the default is a mutable object such as a list, dictionary, or instances of most
classes. For example, the following function accumulates the arguments passed to it on
subsequent calls:

1
2
3
4
5
6
7
def f(a, L=[]):
    L.append(a)
    return L

print f(1)
print f(2)
print f(3)

This will print

1
2
3
[1]
[1, 2]
[1, 2, 3]

我不太明白"只评估一次"在内存管理方面的含义。显然,当函数第一次被调用并存储在一个单独的内存地址中时,即使函数已经结束,函数的默认值也会被计算一次。(根据我的理解,在函数结束后,应该释放所有局部变量?)

我说的对吗?


在Python中,函数也是对象,默认值与函数对象一起存储。默认值不是局部变量;只是当调用函数时,参数在没有给定显式值时绑定到默认值。

当python遇到def ():语句时,它会在那里为您创建一个函数对象;这是"定义时间";该函数不是被调用,只是被创建。然后,在函数对象的属性中计算和存储默认值。

然后,当您调用函数时,默认值已经创建,并且在您没有为参数提供更具体的值时使用。因为默认值与函数对象一起存储,所以可以看到函数调用之间可变对象的更改。

当然,局部变量仍然被清除,但由于它们是引用(python中的所有标识符都是),因此只有当没有其他对象再引用它们时,才会清除它们绑定到的对象。

您可以查看任意函数对象的默认值:

1
2
3
4
5
6
7
8
9
10
11
12
>>> def foo(bar='spam', eggs=[]):
...     eggs.append(bar)
...     return eggs
...
>>> foo.__defaults__
('spam', [])
>>> foo()
['spam']
>>> foo.__defaults__
('spam', ['spam'])
>>> foo() is foo.__defaults__[1]
True

foo()函数有一个__defaults__属性,这是一个默认值的元组,当没有为参数传递值时使用。您可以在调用函数时看到可变列表的变化,并且由于函数返回eggs列表,因此您还可以看到它与该元组中的第二个值是完全相同的对象。

如果不希望共享默认值,而是每次调用函数时都需要一个新的参数值,但没有给定该参数,则需要将默认值设置为sentinel对象。如果您的参数在函数体中仍然设置为该sentinel,则可以执行代码来设置新的默认值。None通常是最佳选择:

1
2
3
def foo(bar='spam', eggs=None):
    if eggs is None:
        eggs = []

如果可以使用None作为非默认值,请使用预先创建的singleton sentinel:

1
2
3
4
5
_sentinel = object()

def foo(bar='spam', eggs=_sentinel):
    if eggs is _sentinel:
        eggs = []

您定义的f函数本身就是一个对象。定义默认值时,这些默认值将绑定到已创建的函数。

您可以看到这一点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> def f(a, L=[]):
...    L.append(a)
...    return L

>>> print id(f)
4419902952

>>> print f.__defaults__
([],)
>>> f(1)
[1]

>>> print id(f)
4419902952

>>> print f.__defaults__
([1],)

进一步编辑,您可以看到列表容器也不会更改:

1
2
3
4
5
6
>>> print id(f.__defaults__[0])
4419887544
>>> f(2)
[1, 2]
>>> print id(f.__defaults__[0])
4419887544

在随后的每个调用中,您的f函数的默认列表("EDOCX1〔9])将附加您的a值。


函数只是Python中的一个对象,它是使用def语法创建的。定义函数时,默认值存储在函数对象中,以后不会重新计算这些值。

这有时用于创建持续到后续调用的函数变量。可以使用__defaults__方法检查函数的默认值。

初始化新对象而不是重用新对象的常见方法是:

1
2
3
4
5
6
def f(a, L=None):
    if L is None:
        L = []

    L.append(a)
    return L

您可以查看此页面了解更多详细信息。


抱歉,这个答案是针对另一个问题的,但如果有人想看,我会把它留在这里作为参考。定义一次意味着在代码执行的第一个点,默认变量被分配给一个对象,这个对象保留在函数对象本身中。

注意,只有1个对象地址被打印,使用默认列表对象。

1
2
3
4
5
  def f(a, L=[]):
    print("id default:", id(L))
    L.append(a)
    print("id used:", id(L)
    return L

注意,打印了两个不同的对象地址,当您在函数内执行l=[]时,您将l绑定到一个不同的列表对象,因此默认的列表对象不会得到更改。

1
2
3
4
5
6
7
def f(a, L=[]):
    print("id default:", id(L))
    if L == []:
        L = []
    L.append(a)
    print("id used:", id(L))      
    return L

上面的函数与下面的函数基本相同,只是使用了none对象而不是空的list对象。

1
2
3
4
5
6
7
def f(a, L=None):
    print("id default", id(L))
    if L is None:
        L = []
   L.append(a)
   print("id used:", id(L))      
   return L