我很难理解算法中问题的根本原因。然后,通过逐步简化函数,我发现Python中对默认参数的求值行为不符合我的预期。
代码如下:
class Node(object): def __init__(self, children = []): self.children = children
问题是children,如果未显式给出属性,则Node类的每个实例都共享相同的属性,例如:
children
>>> n0 = Node() >>> n1 = Node() >>> id(n1.children) Out[0]: 25000176 >>> id(n0.children) Out[0]: 25000176
我不了解此设计决定的逻辑吗?为什么Python设计人员决定在定义时评估默认参数?这对我来说似乎很违反直觉。
另一种选择是重量级的-将“默认参数值”存储为函数对象的“ thunk”代码,每次调用该函数时都将为其重复执行而没有为该参数指定值- 并将使其早期绑定(在def时绑定)要困难得多,这通常是您想要的。例如,在存在的Python中:
def ack(m, n, _memo={}): key = m, n if key not in _memo: if m==0: v = n + 1 elif n==0: v = ack(m-1, 1) else: v = ack(m-1, ack(m, n-1)) _memo[key] = v return _memo[key]
…编写类似于上面的记忆功能是一项基本任务。类似地:
for i in range(len(buttons)): buttons[i].onclick(lambda i=i: say('button %s', i))
…i=i依赖默认arg值的早期绑定(定义时间)的简单方法是获取早期绑定的简单方法。因此,当前规则是简单,直接的,它使您可以用一种非常容易解释和理解的方式来做所有您想做的事情:如果您想晚绑定一个表达式的值,请在函数体内评估该表达式;如果要早期绑定,请将其评估为arg的默认值。
i=i
如上述示例所示,另一种方法在两种情况下都要求延迟绑定,但不能提供这种灵活性,并且会迫使您每次需要早期绑定时都要经历一次麻烦(例如将函数包装到闭包工厂中)。这个假想的设计决定(除了在整个位置生成并反复评估击打的“隐形”击打之外),使程序员不得不承担更大的重量级样板。
换句话说,“应该有一种,最好只有一种明显的方法[1]”:当您想要后期绑定时,已经有一种非常明显的方法来实现它(因为该函数的所有代码仅被执行在调用时,显然一切评价 还有 就是后期绑定); 让default- arg评估产生早期绑定也为您提供了一种实现早期绑定的明显方法(一个加号!!),而不是给了两种明显的方法来进行晚期绑定,而没有给出一种明显的方法来进行早期绑定(减号!!)。
[1]:“尽管起初,除非您是荷兰人,否则这种方式可能并不明显。”