带默认参数的函数问题

1 投票
2 回答
888 浏览
提问于 2025-04-16 11:14

我有一些关于带默认参数的函数的问题。

import sys
from random import randint

my_list = [1,2,3]
def Veli(a, b = my_list):
    my_list.append(randint(1,1500))
    print a + b[-1]

print "Number of Refs:", sys.getrefcount(my_list)
print "Def args:", Veli.func_defaults
Veli(1) # This is 4
Veli(1) # We almost always obtain different result because of randint
Veli(1) # Again different results.
print Veli.func_defaults
print "Number of Refs:", sys.getrefcount(my_list)
Veli(1)
my_list = [-5] # old my_list has different address 
print "Number of Refs:", sys.getrefcount(my_list) # Less than previous
print "Def args:", Veli.func_defaults
Veli(1) # Now gives same results.
print "Def args:", Veli.func_defaults
Veli(1) # Same result again...


输出结果:(一些数字的结果取决于randint返回的值,当然。)

引用次数:3
默认参数:([1, 2, 3],)
322
1119
740
([1, 2, 3, 321, 1118, 739],)
引用次数:3
303
引用次数:2
默认参数:([1, 2, 3, 321, 1118, 739, 302],)
303
默认参数:([1, 2, 3, 321, 1118, 739, 302],)
303


下面的代码会输出一个比之前小的数字,因为b和my_list不再指向同一个地址。

print "#ref:", sys.getrefcount(my_list) # Less than previous

现在,我们有一种方法(唯一的方法?)来访问b,函数的默认参数:

Veli.func_defaults[0]


抱歉我解释得这么长。以下是我的问题:

  1. 这算是个问题吗?我的模块中的字典覆盖了my_list变量,现在my_list的地址和之前不同了。使用全局变量my_list的函数在变化(增长),而我的默认参数却没有。为什么在执行b + b[-1]这个表达式时,b看不到名为my_list的全局变量?我知道,b和my_list的地址不同(因为相互对象(比如列表)保证指向不同、唯一的新创建的列表),但为什么Python的实现让b在作为函数参数时看不到全局变量?你能详细解释一下吗?

  2. 有没有办法通过Veli.func_defaults[0]获得相同的结果?这段代码执行后,假设我想改变我名为Veli的函数的默认参数。我不能用my_list来做到这一点,因为my_list和b的地址不同。一个方法是改变列表的元素,Veli.func_defaults[0]。还有其他方法吗?

  3. (这与上面的代码关系不大)我怎么能获取变量的地址?例如,如何获取b的地址?我使用内置函数,比如__hash__,但应该有更合适的方法。


备注:

a) 这些代码可能出于某种原因没什么用,但我想了解大家的看法。
b) Python 2.6.6 (r266:84292, 2010年9月15日,15:52:39)
[GCC 4.4.5] 在linux2上

2 个回答

1

我建议你看看这个已有的问题,因为你的问题其实和那个问题是同一个类型的,只是角度稍微不同。

正如你所提到的,当函数的定义被执行时,b会指向my_list。当你之后把my_list改为指向另一个对象时,b不会受到影响。

如果你想实现“懒绑定”,你可以这样写你的函数:

def Veli(a, b=None):
    if b is None:
        b = my_list # Gets value of my_list at call time
    my_list.append(randint(1,1500))
    print a + b[-1]
1
  1. 默认参数是在定义函数的时候就确定的。比如这一行 def Veli(a, b = my_list):,它把当时 my_list 指向的对象的引用放到了 func_defaults 里。Python 其实是不会用引用传递的——变量保存的是引用,而这些引用都是按值传递的。所以,实际上保存在 b 里的,是一个引用(指针)的副本,没人记得它是从哪里来的,也没人会去更新它。
  2. 不太确定你在问什么,请澄清一下。如果没有传第二个参数,b 就会是 Veli.func_defaults[0],但如果传了第二个参数,那当然就不一样了(当然,前提是调用者没有访问 func_defaults...你应该假设他没有)。你可以像其他答案建议的那样,把 b 的默认值设为 None,然后如果 b 是 None,就使用全局的 my_list——这样每次调用时就能得到一个最新的引用副本。也许你应该写一个类,把默认值作为 self 的一个属性,并使用常见的 (... = None): if ... is None: use = default 这种写法。
  3. 内置函数 id。其实它返回的是什么是由实现决定的(不一定是地址),只要它是一个表示对象身份的整数,也就是说,不同的对象(即使它们的生命周期有重叠)会有不同的 id,而同一个对象在其生命周期内总是会返回相同的 id。CPython 选择的简单返回值是地址。

撰写回答