首页 > 解决方案 > 压缩和应用函数列表和相应参数的 TypeError

问题描述

问题:

f1,f2成为其他参数的两个函数x和不同数量的其他参数;他们实际上做了什么是无关紧要的,但我将使用 delta 函数来说明:

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

wherechoice_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使用一些参数打印返回列表 (so) 的第二个函数:

vs = [(1., 2., 3.), (4., 5.)]

test0 = f(f1, f2, 2)

test = test0[1]
print(test(1., vs))

f1有 4 个参数,f2有 3 个参数,一切似乎都很好,按照任何逻辑,这段代码都应该 return 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

这给出了相同的结果。

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

令人惊讶的是,似乎没有任何问题。这是所有这些prints的输出i = 1

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仍然存在。

我在这里束手无策。我做错了什么,如何使此代码正常运行?有没有更健壮和/或优雅的方式来做到这一点?

标签: pythonfunctionargumentslist-comprehension

解决方案


这是一个常见的错误。Python 使用词法作用域闭包,因此在嵌套列表推导中,您的 lambdafn从外部列表推导中捕获为局部变量:

aux2 = [lambda x, args: sum(
        [fn(x, *arg) for fn, arg in zip(fns, args)]
        ) for fns in aux1]

这个变量fns

但是在lambda评估时,您是否已经完成了迭代aux1,所以fns指的是所有 lambda所产生的最后一个值。aux1

您必须注意在循环中创建闭包。修复很简单,创建另一个封闭范围:

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 的人来说都是相对冗长的。另外,请注意, to 的参数sum可以是生成器表达式,以免在将整个列表传递给sum您需要的列表理解之前实现整个列表(它被急切地评估):

    aux2 = [
        (lambda _fns: lambda x, args: 
            sum( fn(x, *arg) for fn, arg in zip(_fns, args) )
        )(fns)
        for fns in aux1
    ]

推荐阅读