6 回答
TA贡献2016条经验 获得超9个赞
假设您有以下代码
fruits = ("apples", "bananas", "loganberries")
def eat(food=fruits):
...
当我看到吃的声明时,最令人惊讶的是认为如果没有给出第一个参数,它将等于元组 ("apples", "bananas", "loganberries")
但是,假设后面的代码,我会做类似的事情
def some_random_function():
global fruits
fruits = ("blueberries", "mangos")
然后,如果默认参数在函数执行而不是函数声明中被绑定,那么我会惊讶地发现水果已被改变(以非常糟糕的方式)。这比发现foo上面的函数改变列表更令人惊讶的IMO 。
真正的问题在于可变变量,并且所有语言在某种程度上都存在这个问题。这是一个问题:假设在Java中我有以下代码:
StringBuffer s = new StringBuffer("Hello World!");
Map<StringBuffer,Integer> counts = new HashMap<StringBuffer,Integer>();
counts.put(s, 5);
s.append("!!!!");
System.out.println( counts.get(s) ); // does this work?
现在,我的地图StringBuffer在放入地图时是否使用了键的值,还是通过引用存储了键?无论哪种方式,有人感到惊讶; 尝试将对象从Map使用中取出的值与他们放入的对象相同的人,或者即使他们使用的键实际上是同一个对象而无法检索其对象的人用于将其放入映射的(这实际上是Python不允许其可变内置数据类型用作字典键的原因)。
你的例子是一个很好的例子,Python新人会感到惊讶和被咬。但我认为,如果我们“修复”这个,那么这只会产生一种不同的情况,即他们会被咬伤,而那种情况甚至会更不直观。而且,在处理可变变量时总是如此; 你总是遇到一些情况,根据他们正在编写的代码,某人可能直观地期望一种或相反的行为。
我个人喜欢Python当前的方法:默认函数参数在定义函数时进行评估,并且该对象始终是默认值。我想他们可以使用空列表进行特殊情况,但这种特殊的外壳会引起更多的惊讶,更不用说倒退不兼容了
TA贡献1788条经验 获得超4个赞
我对Python解释器内部工作一无所知(我也不是编译器和解释器方面的专家)所以如果我提出任何不可知或不可能的建议,请不要责怪我。
如果python对象是可变的,我认为在设计默认参数时应该考虑到这一点。实例化列表时:
a = []
你希望得到一个新的列表引用a
。
为什么要a=[]
进去
def x(a=[]):
在函数定义上实例化一个新列表而不是在调用上?就像你问“用户是否提供参数然后实例化一个新列表并使用它就好像它是由调用者生成”一样。我认为这是模棱两可的:
def x(a=datetime.datetime.now()):
用户,是否要a
默认为与定义或执行时相对应的日期时间x
?在这种情况下,与前一个一样,我将保持相同的行为,就好像默认参数“assignment”是函数的第一条指令(datetime.now()
在函数调用上调用)。另一方面,如果用户想要定义时间映射,他可以写:
b = datetime.datetime.now()def x(a=b):
我知道,我知道:这是一个封闭。或者,Python可能会提供一个关键字来强制定义时绑定:
def x(static a=b):
TA贡献1906条经验 获得超10个赞
嗯,原因很简单,在执行代码时完成绑定,并且执行函数定义,以及......定义函数时。
比较一下:
class BananaBunch: bananas = [] def addBanana(self, banana): self.bananas.append(banana)
此代码遭受完全相同的意外事件。bananas是一个类属性,因此,当您向其添加内容时,它会添加到该类的所有实例中。原因完全一样。
这只是“如何工作”,并且在功能案例中使其工作方式可能很复杂,并且在类的情况下可能不可能,或者至少减慢对象实例化的速度,因为你必须保持类代码并在创建对象时执行它。
是的,这是出乎意料的。但是一旦下降了,它就完全适合Python的工作方式。事实上,它是一个很好的教学辅助工具,一旦你理解了为什么会这样,你就会更好地理解python。
这说它应该在任何优秀的Python教程中占据突出地位。因为正如你所提到的,每个人迟早都会遇到这个问题。
TA贡献1797条经验 获得超4个赞
我曾经认为在运行时创建对象将是更好的方法。我现在不太确定,因为你确实失去了一些有用的功能,尽管它可能是值得的,不管只是为了防止新手混淆。这样做的缺点是:
1.表现
def foo(arg=something_expensive_to_compute())):
...
如果使用了调用时评估,则每次使用函数时都会调用昂贵的函数而不使用参数。您要么为每次调用付出昂贵的代价,要么需要在外部手动缓存该值,污染您的命名空间并添加详细程度。
2.强制绑定参数
一个有用的技巧是在创建lambda时将lambda的参数绑定到变量的当前绑定。例如:
funcs = [ lambda i=i: i for i in range(10)]
这将返回分别返回0,1,2,3 ...的函数列表。如果行为发生了变化,它们将绑定i到i 的调用时间值,因此您将获得所有返回的函数列表9。
否则实现此方法的唯一方法是使用i绑定创建进一步的闭包,即:
def make_func(i): return lambda: i
funcs = [make_func(i) for i in range(10)]
3.内省
考虑一下代码:
def foo(a='test', b=100, c=[]):
print a,b,c
我们可以使用inspect模块获取有关参数和默认值的信息
>>> inspect.getargspec(foo)
(['a', 'b', 'c'], None, None, ('test', 100, []))
这些信息对于文档生成,元编程,装饰器等非常有用。
现在,假设可以更改默认值的行为,以便这相当于:
_undefined = object() # sentinel value
def foo(a=_undefined, b=_undefined, c=_undefined)
if a is _undefined: a='test'
if b is _undefined: b=100
if c is _undefined: c=[]
但是,我们已经失去了内省的能力,并且看到了默认参数是什么。因为没有构造对象,所以我们不能在没有实际调用函数的情况下获取它们。我们能做的最好的事情是存储源代码并将其作为字符串返回。
添加回答
举报