问题:
f1
,f2
是两个x
函数和不同数量的其他参数;它们实际做什么无关紧要,但我将使用增量函数进行说明:
def f1(x, p1, p2, v):
if x == p1:
return v
elif x == p2:
return v
else:
return 0
def f2(x, p1, v):
if x == p1:
return v
else:
return 0
我现在想构造一个函数,该函数返回x
函数列表,这些函数是两个函数的各种组合的总和:
import itertools
def choice_matrix(n):
return itertools.product(*[range(n)]*n)
def f(fn1, fn2, n):
aux1 = [[fn1 if ae == 0 else fn2 for ae in ar] for ar in choice_matrix(n)]
aux2 = [
lambda x, args: sum(
[fn(x, *arg) for fn, arg in zip(fns, args)]
) for fns in aux1
]
return aux2
其中choice_matrix
只是一个函数,它返回n
位置的两个元素的各种组合:
>>> cm = list(choice_matrix(2))
>>> for i in range(len(cm)): print(cm)
(0, 0)
(0, 1)
(1, 0)
(1, 1)
通过从函数打印,我可以看到aux1
按预期工作,并且确实返回了一个函数矩阵:
[<function f1 at 0x7efbdec08730>, <function f1 at 0x7efbdec08730>]
[<function f1 at 0x7efbdec08730>, <function f2 at 0x7efbdec087b8>]
[<function f2 at 0x7efbdec087b8>, <function f1 at 0x7efbdec08730>]
[<function f2 at 0x7efbdec087b8>, <function f2 at 0x7efbdec087b8>]
作为测试,我现在尝试使用一些参数打印返回列表的第二个函数(所以f1 + f2
(:
vs = [(1., 2., 3.), (4., 5.)]
test0 = f(f1, f2, 2)
test = test0[1]
print(test(1., vs))
f1
有 4 个参数,f2
有 3 个参数,一切似乎都很好,根据任何逻辑,这段代码都应该返回3.0
。然而,我得到的是一个TypeError
;该函数采用的位置参数比给定的要少:
Traceback (most recent call last):
File "test.py", line 323, in <module>
print(test(1., vs))
File "test.py", line 283, in <lambda>
arg in zip(aux1[i], args)])
File "test.py", line 283, in <listcomp>
arg in zip(aux1[i], args)])
TypeError: f2() takes 3 positional arguments but 4 were given
到目前为止我尝试过:
- 类似的列表理解:
def f(fn1, fn2, n):
aux1 = [[fn1 if ae == 0 else fn2 for ae in ar] for ar in choice_matrix(n)]
for i in range(len(aux1)): print(aux1[i])
aux2 = [
lambda x, args: list(
map(
lambda fn, arg: fn(x, *arg), fns, args
)
) for fns in aux1
]
return aux2
这给出了相同的结果。
- 完全解构
aux2
其基本组件操作,看看出了什么问题:
def f(fn1, fn2, n):
aux1 = [[fn1 if ae == 0 else fn2 for ae in ar] for ar in choice_matrix(n)]
aux2 = {}
for i in range(len(aux1)):
print(i)
print(aux1[i])
print(set(zip(aux1[i], vs)))
aux2[i] = lambda x, args: sum([fn(x, *arg) for fn,
arg in zip(aux1[i], args)])
if i == 1: fun = aux2[i]; print(fun(1., vs))
print('')
return aux2
令人惊讶的是,似乎没有任何问题。这是i = 1
的所有print
的输出:
1
[<function f1 at 0x7efbdef23730>, <function f2 at 0x7efbdef237b8>]
{(<function f2 at 0x7efbdef237b8>, (4.0, 5.0)), (<function f1 at 0x7efbdef23730>, (1.0, 2.0, 3.0))}
3.0
因此,函数及其参数排列在一起以正确压缩,正确压缩,正确应用并返回预期值,但我仍然得到完全相同的TypeError
因此,一旦参数传递到函数外部,压缩似乎就会中断。
- 避免压缩的伪函数重写:
def f(fn1, fn2, n):
aux1 = [[fn1 if ae == 0 else fn2 for ae in ar] for ar in choice_matrix(n)]
def a(fns, x, args):
if len(fns) == 0 or len(args) == 0:
return []
else:
fhead, *ftail = fns
ahead, *atail = args
return [fhead(x, *ahead)] + a(ftail, x, atail)
for i in range(len(aux1)):
print('i =', i)
print(aux1[i])
if i == 1: print(a(aux1[i], 1., vs)); print(sum(a(aux1[i], 1., vs)))
print('')
aux2 = [lambda x, args: a(fs, x, args) for fs in aux1]
return aux2
内部打印输出给出了预期的结果:
i = 1
[<function f1 at 0x7efbdec19730>, <function f2 at 0x7efbdec197b8>]
[3.0, 0]
3.0
然而,TypeError
仍然存在。
我在这里已经穷到了尽头。我做错了什么,如何使这段代码正常运行?有没有更强大和/或更优雅的方法?
这是一个常见的错误。Python 使用词法范围闭包,因此在嵌套列表推导中,lambda 会捕获fn
作为外部列表推导中的局部变量:
aux2 = [lambda x, args: sum(
[fn(x, *arg) for fn, arg in zip(fns, args)]
) for fns in aux1]
此变量fns
.
但是当lambda
被评估时,你已经完成了对aux1
的迭代,所以fns
指的是aux1
为所有lambda产生的最后一个值。
您必须注意在循环中创建闭包。修复很简单,创建另一个封闭范围:
def f(fn1, fn2, n):
aux1 = [[fn1 if ae == 0 else fn2 for ae in ar] for ar in choice_matrix(n)]
aux2 = [(lambda _fns: lambda x, args: sum(
[fn(x, *arg) for fn, arg in zip(_fns, args)]
))(fns)
for fns in aux1]
return aux2
我敦促你不要那么简洁,python 风格对于任何习惯的人来说都是相对冗长的,比如说,Haskell。另外,请注意,sum
参数可以是生成器表达式,以免在将整个列表传递给sum
之前实现整个列表,就像您需要列表理解一样(它被急切地评估(:
aux2 = [
(lambda _fns: lambda x, args:
sum( fn(x, *arg) for fn, arg in zip(_fns, args) )
)(fns)
for fns in aux1
]