Mer*_*glu 35 python logic language-design
我很难理解算法中问题的根本原因.然后,通过逐步简化函数,我发现在Python中对默认参数的评估并不像我预期的那样.
代码如下:
class Node(object):
def __init__(self, children = []):
self.children = children
Run Code Online (Sandbox Code Playgroud)
问题是children如果没有明确给出属性,Node类的每个实例都共享相同的属性,例如:
>>> n0 = Node()
>>> n1 = Node()
>>> id(n1.children)
Out[0]: 25000176
>>> id(n0.children)
Out[0]: 25000176
Run Code Online (Sandbox Code Playgroud)
我不明白这个设计决定的逻辑?为什么Python设计者决定在定义时评估默认参数?这对我来说似乎非常违反直觉.
Ale*_*lli 37
替代方案将是非常重量级 - 在函数对象中存储"默认参数值"作为代码的"thunks",每次调用函数时都会反复执行而没有为该参数指定的值 - 并且会使它成为更难获得早期绑定(在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]
Run Code Online (Sandbox Code Playgroud)
...像上面那样编写一个记忆功能是一项非常基本的任务.同理:
for i in range(len(buttons)):
buttons[i].onclick(lambda i=i: say('button %s', i))
Run Code Online (Sandbox Code Playgroud)
...简单i=i,依赖于默认arg值的早期绑定(定义时间),是获得早期绑定的一种简单方法.因此,当前规则简单,直接,并且允许您以一种非常容易解释和理解的方式执行您想要的任何操作:如果您希望对表达式的值进行后期绑定,请在函数体中计算该表达式; 如果您想要早期绑定,请将其评估为arg的默认值.
替代方案,强制两种情况的后期绑定,不会提供这种灵活性,并且会在每次需要早期绑定时强制您通过环(例如将函数包装到闭包工厂),如上例所示 - 通过这个假设的设计决策强迫程序员使用更多的重量级样板(超出"无形"产生并重复评估整个地方的thunk).
换句话说,"应该有一个,最好只有一个,显而易见的方法[1]":当你想要后期绑定时,已经有一种非常明显的方法来实现它(因为所有函数的代码都只执行了)在调用时,显然一切评价还有就是后期绑定); 有default-arg评估产生早期绑定为你提供了一种明显的方法来实现早期绑定(加上! - ),而不是给出两种明显的方法来获得后期绑定,而没有明显的方法来获得早期绑定(减去! - ).
[1]:"虽然这种方式起初可能并不明显,除非你是荷兰人."
问题是这个.
每次调用函数时,将函数计算为初始化函数的成本太高.
0是一个简单的文字.评估一次,永远使用它.
int 是一个函数(如列表),每次需要作为初始化程序时都必须进行评估.
构造[]是文字的,就像0,意思是"这个确切的对象".
问题是,有些人希望它意味着list"请为我评估此函数,以获取作为初始化器的对象".
添加必要的if陈述以便始终进行此评估将是一个沉重的负担.最好将所有参数作为文字,而不是在尝试进行功能评估时进行任何额外的功能评估.
另外,从根本上说,在技术上不可能将参数默认值实现为函数评估.
考虑一下这种循环的递归恐怖.假设我们不是默认值是文字,而是允许它们成为每次需要参数默认值时评估的函数.
[这将与collections.defaultdict工作方式平行.]
def aFunc( a=another_func ):
return a*2
def another_func( b=aFunc ):
return b*3
Run Code Online (Sandbox Code Playgroud)
有什么价值another_func()?要获取默认值b,必须进行评估aFunc,这需要eval another_func.哎呀.
这里讨论的解决方法(非常可靠)是:
class Node(object):
def __init__(self, children = None):
self.children = [] if children is None else children
Run Code Online (Sandbox Code Playgroud)
至于为什么要从vonLöwis寻找答案,但可能是因为函数定义由于Python的体系结构而产生代码对象,并且可能没有在默认参数中使用这样的引用类型的工具.
当然在你的情况下很难理解.但是你必须看到,每次评估默认args会给系统带来沉重的运行时负担.
另外你应该知道,在容器类型的情况下可能会出现这个问题 - 但你可以通过使事物明确来规避它:
def __init__(self, children = None):
if children is None:
children = []
self.children = children
Run Code Online (Sandbox Code Playgroud)
这源于python对语法和执行简单性的强调。def 语句出现在执行过程中的某个点。当 Python 解释器到达该点时,它会评估该行中的代码,然后从函数体创建一个代码对象,该对象将在您稍后调用该函数时运行。
这是函数声明和函数体之间的简单拆分。声明在代码中到达时执行。主体在调用时执行。请注意,每次到达时都会执行声明,因此您可以通过循环创建多个函数。
funcs = []
for x in xrange(5):
def foo(x=x, lst=[]):
lst.append(x)
return lst
funcs.append(foo)
for func in funcs:
print "1: ", func()
print "2: ", func()
Run Code Online (Sandbox Code Playgroud)
已经创建了五个单独的函数,每次执行函数声明时都会创建一个单独的列表。在每次循环通过时funcs,相同的函数在每次通过时执行两次,每次使用相同的列表。这给出了结果:
1: [0]
2: [0, 0]
1: [1]
2: [1, 1]
1: [2]
2: [2, 2]
1: [3]
2: [3, 3]
1: [4]
2: [4, 4]
Run Code Online (Sandbox Code Playgroud)
其他人为您提供了解决方法,即使用 param=None,如果值为 None,则在正文中分配一个列表,这是完全惯用的 Python。有点难看,但是简单强大,解决方法也不是很痛苦。
编辑添加:有关此的更多讨论,请参阅 effbot 的文章:http ://effbot.org/zone/default-values.htm和语言参考,此处:http ://docs.python.org/reference/ Compound_stmts.html#function
我认为这也是违反直觉的,直到我学会了Python如何实现默认参数.
函数是一个对象.在加载时,Python创建函数对象,评估def语句中的默认值,将它们放入元组中,并将该元组添加为名为的函数的属性func_defaults.然后,当调用一个函数时,如果调用没有提供值,Python会抓取默认值func_defaults.
例如:
>>> class C():
pass
>>> def f(x=C()):
pass
>>> f.func_defaults
(<__main__.C instance at 0x0298D4B8>,)
Run Code Online (Sandbox Code Playgroud)
因此,所有f不提供参数的调用都将使用相同的实例C,因为这是默认值.
至于为什么Python以这种方式做到这一点:好吧,该元组可以包含每次需要默认参数值时都会调用的函数.除了明显的性能问题之外,您还开始涉及特殊情况,例如存储文字值而不是非可变类型的函数,以避免不必要的函数调用.当然,还有很多性能影响.
实际行为非常简单.如果您希望在运行时通过函数调用生成默认值,那么有一个简单的解决方法:
def f(x = None):
if x == None:
x = g()
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
5439 次 |
| 最近记录: |