32

当您在 Python 中使用数组参数定义函数时,该参数的范围是什么?

此示例取自 Python 教程:

def f(a, L=[]):
    L.append(a)
    return L

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

印刷:

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

我不太确定我是否理解这里发生的事情。这是否意味着数组的范围在函数之外?为什么数组会记住每次调用的值?来自其他语言,只有当变量是静态的时,我才会期望这种行为。否则,它似乎应该每次都重置。实际上,当我尝试以下操作时:

def f(a):
    L = []
    L.append(a)
    return L

我得到了我期望的行为(每次调用都重置了数组)。

所以在我看来,我只需要解释这一行-变量def f(a, L=[]):的范围是什么?L

4

7 回答 7

25

范围如您所料。

可能令人惊讶的是,默认值只计算一次并重复使用,因此每次调用函数时都会得到相同的列表,而不是初始化为 [] 的新列表。

该列表存储在f.__defaults__(或f.func_defaultsPython 2 中)。

def f(a, L=[]):
    L.append(a)
    return L

print f(1)
print f(2)
print f(3)
print f.__defaults__
f.__defaults__ = (['foo'],) # Don't do this!
print f(4)

结果:

[1]
[1, 2]
[1, 2, 3]
([1, 2, 3],)
['foo', 4]
于 2010-02-25T15:28:08.393 回答
7

变量的范围与L您预期的一样。

“问题”出在您使用[]. Python 不会在您每次调用该函数时创建一个新列表。 L每次调用时都会分配相同的列表,这就是函数“记住”以前调用的原因。

所以实际上这就是你所拥有的:

mylist = []
def f(a, L=mylist):
    L.append(a)
    return L

Python 教程是这样说的:

默认值仅评估一次。当默认值是可变对象(例如列表、字典或大多数类的实例)时,这会有所不同。

并建议采用以下方式对预期行为进行编码:

def f(a, L=None):
    if L is None:
        L = []
    L.append(a)
    return L
于 2010-02-25T15:29:24.367 回答
3

“魔法”比你想象的还要少。这相当于

m = []

def f(a, L=m):
    L.append(a)
    return L

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

m只创建一次。

于 2010-02-25T15:30:47.980 回答
2

假设您有以下代码:

def func(a=[]):
    a.append(1)
    print("A:", a)

func()
func()
func()

你可以使用 python 的缩进来帮助你理解发生了什么。当文件被执行时,所有与左边距齐平的东西都会被执行。缩进的所有内容都被编译成一个代码对象,该对象在func()被调用时被执行。所以当程序被执行时,函数被定义并且它的默认参数设置一次(因为def语句是左对齐的)。

它对默认参数的作用是一个有趣的问题。在 python 3 中,它将有关函数的大部分信息放在两个地方: func.__code__func.__defaults__. 在 python 2 中,func.__code__是. python 2 的更高版本,包括 2.6 都有两组名称,以帮助从 python 2 过渡到 python 3。我将使用更现代的和. 如果您被困在较旧的 python 上,则概念是相同的;只是名称不同。 func.func_code func.__defaults__func.func_defaults__code____defaults__

默认值存储在 中func.__defaults__,并在每次调用函数时检索。

因此,当您在上面定义函数时,函数的主体将被编译并存储在 下的变量中__code__,以便稍后执行,并且默认参数存储在__defaults__. 当您调用该函数时,它使用__defaults__. 如果这些值因任何原因被修改,它只有修改后的版本可供使用。

尝试在交互式解释器中定义不同的函数,看看你能弄清楚 python 如何创建和使用函数。

于 2010-02-25T16:31:39.777 回答
1

解释在这个问题的答案中给出。在这里总结一下:

Python 中的函数是一种对象。因为它们是一种对象,所以在实例化时它们的行为类似于对象。一个函数,如果使用可变属性作为​​默认参数定义,则与具有可变列表的静态属性的类完全相同。

Lennart Regebro 有一个很好的解释,Roberto Liffredo对这个问题的回答非常好。

为了适应 Lennart 的回答……如果我有BananaBunch课:

class BananaBunch:
    bananas = []

    def addBanana(self, banana):
        self.bananas.append(banana)


bunch = BananaBunch()
>>> bunch
<__main__.BananaBunch instance at 0x011A7FA8>
>>> bunch.addBanana(1)
>>> bunch.bananas
[1]
>>> for i in range(6):
    bunch.addBanana("Banana #" + i)
>>> for i in range(6):
    bunch.addBanana("Banana #" + str(i))

>>> bunch.bananas
[1, 'Banana #0', 'Banana #1', 'Banana #2', 'Banana #3', 'Banana #4', 'Banana #5']

// And for review ... 
//If I then add something to the BananaBunch class ...
>>> BananaBunch.bananas.append("A mutated banana")

//My own bunch is suddenly corrupted. :-)
>>> bunch.bananas
[1, 'Banana #0', 'Banana #1', 'Banana #2', 'Banana #3', 'Banana #4', 'Banana #5', 'A mutated banana']

这如何应用于函数? Python 中的函数是对象。这值得重复。Python 中的函数是 object

所以当你创建一个函数时,你就是在创建一个对象。当你给一个函数一个可变的默认值时,你正在用一个可变的值填充该对象的属性,并且每次调用该函数时,你都在对同一个属性进行操作。因此,如果您正在使用可变调用(如 append),那么您正在修改同一个对象,就像您向bunch对象添加香蕉一样。

于 2010-02-25T15:40:16.207 回答
0

这里的“问题”L=[]是只评估一次,即编译文件时。Python 逐行遍历文件的每一行并对其进行编译。当它使用默认参数到达时def,它会创建一次该列表实例。

如果放在L = []函数代码里面,实例不是在“编译时”创建的(实际上编译时也可以称为运行时的一部分),因为 Python 编译函数的代码但不调用它。因此,您将获得一个的列表实例,因为每次调用该函数时都会完成创建(而不是在编译期间一次)。

该问题的解决方案是不使用可变对象作为默认参数,或者仅使用固定实例,例如None

def f(a, L = None):
    if l == None:
        l = []
    L.append(a)
    return L

请注意,在您描述的两种情况下,范围L都是函数范围。

于 2010-02-25T15:39:53.690 回答
-1

您必须记住,python 是一种解释性语言。这里发生的是当定义函数“f”时,它创建列表并将其分配给函数“f”的默认参数“L”。稍后,当您调用此函数时,将使用相同的列表作为默认参数。简而言之,“def”行上的代码仅在定义函数时执行一次。这是一个常见的python陷阱,我自己也曾陷入其中。

def f(a, L=[]):
    L.append(a)
    return L

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

在此处的其他答案中已针对习语提出了建议以解决此问题。我建议如下:

def f(a, L=None):
    L = L or []
    L.append(a)
    return L

这使用 or 短路来获取通过的“L”,或者创建一个新列表。

范围问题的答案是“L”在函数“f”内只有一个范围,但是因为默认参数只分配给一个列表一次,而不是每次调用函数时它的行为就像默认参数一样“L”具有全局范围。

于 2010-02-25T15:52:50.993 回答