TypeError при архивировании и применении списка функций и соответствующих аргументов - PullRequest
1 голос
/ 12 января 2020

Проблема:

Позвольте f1, f2 быть двумя функциями x и другим количеством других аргументов; то, что они на самом деле делают, не имеет значения, но я буду использовать дельта-функции Dira c для иллюстрации:

def f1(x, p1, p2, p3):
    if   x == p1:
        return p3
    elif x == p2:
        return p3
    else:
        return 0

def f2(x, p1, p2):
    if   x == p1:
        return p2
    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, все выглядит хорошо, и этот код должен, по любым логикам c, возвращать 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

Удивительно ничего , кажется, идет не так. Это выходные данные всех этих print s для 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 сохраняется.

Я в своем уме конец здесь. Что я сделал неправильно и как я могу сделать этот код функционировать должным образом? Есть ли более надежный и / или элегантный способ сделать это?

1 Ответ

1 голос
/ 13 января 2020

Это распространенная ошибка. Python использует лексические замыкания, поэтому в вашем понимании вложенного списка ваша лямбда захватывает fn в качестве локальной переменной из понимания внешнего списка:

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

Эта переменная fns.

Но к моменту оценки lambda вы уже закончите итерацию по aux1, поэтому fns относится к значению last , полученному aux1 для всех ваших лямбд.

Вы должны позаботиться о создании замыканий в oop. Исправление простое, создавая еще одну вмещающую область:

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
    ]
...