Python中默认参数的范围是什么?

时间:2010-02-25 15:26:36

标签: python scope parameters default-value function-calls

使用数组参数在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变量的范围是什么?

7 个答案:

答案 0 :(得分:23)

范围如您所料。

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

该列表存储在f.func_defaults

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

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

结果:

[1]
[1, 2]
[1, 2, 3]
([1, 2, 3],)
['foo', 4]

答案 1 :(得分:6)

L变量的范围符合您的预期。

“问题”与您使用[]创建的列表有关。每次调用函数时,Python都不会创建新列表。每次调用时L都会被分配相同的列表,这就是函数“记住”之前调用的原因。

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

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

Python Tutorial puts it this way

  

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

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

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

答案 2 :(得分:3)

甚至比您可能怀疑的“神奇”还要少。这相当于

m = []

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

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

m仅创建一次。

答案 3 :(得分:1)

假设您有以下代码:

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__ func.func_code func.__defaults__func.func_defaults。更高版本的python 2,包括2.6都有两组名称,以帮助从python 2到python 3的转换。我将使用更现代的__code____defaults__。如果你被困在一个较老的python上,概念是相同的;只是名字不同。

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

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

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

答案 4 :(得分:0)

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

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

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

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

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

答案 5 :(得分:0)

this question的答案给出了解释。总结一下:

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

Lennart Regebro a good explanation和Roberto Liffredo的answer to the question非常出色。

要适应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']

这如何适用于功能? Functions in Python are objects。这需要重复。 Functions in Python are object秒。

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

答案 6 :(得分:-1)

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

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

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

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