Объединение нескольких лямбда-функций в одну

Предположим, есть список словарей, состоящих из лямбда функций:

funcs = [{'1': lambda a, b: a + b > 5}, {'1': lambda a, b: b > a},
         {'2': lambda a, b: a * b > 10}, {'2': lambda a, b: a - b == 4}]

Нужно создать новый словарь с лямбда функциями таким образом, чтобы объединить через оператор 'and' все лямбда-функции, содержащиеся по данному ключу. Если сделать это "руками", то все работает как надо:

funcs_combine_1 = {}
funcs_combine_1['1'] = lambda a, b: funcs[0]['1'](a, b) and funcs[1]['1'](a, b)
funcs_combine_1['2'] = lambda a, b: funcs[2]['2'](a, b) and funcs[3]['2'](a, b)

print(funcs_combine_1['1'](3, 8))
print(funcs_combine_1['1'](1, 3))
print(funcs_combine_1['2'](6, 2))
print(funcs_combine_1['2'](5, 1))
True
False
True
False

А если попробовать написать код, чтобы автоматически находились и объединялись лямбда функции по ключам:

funcs_combine_2 = {'1': lambda a, b: True, '2': lambda a, b: True}
for val in funcs:
    for key in val.keys():
        funcs_combine_2[key] = lambda a, b: funcs_combine_2[key](a, b) and val[key](a, b)

то появляется ошибка вида:

    funcs_combine_2[key] = lambda a, b: funcs_combine_2[key](a, b) and val[key](a, b)
                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^
  [Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded

Как можно исправить этот код, чтобы лямбда функции объединялись по ключам через оператор 'and' автоматически?

Другими словами, из списка

funcs = [{'1': lambda a, b: a + b > 5}, {'1': lambda a, b: b > a},
         {'2': lambda a, b: a * b > 10}, {'2': lambda a, b: a - b == 4}]

должен получиться список:

funcs = [{'1': lambda a, b: a + b > 5 and b > a},
         {'2': lambda a, b: a * b > 10 and a - b == 4}]

Спасибо!!


Ответы (2 шт):

Автор решения: CrazyElf
funcs_combine_2[key] = lambda a, b: funcs_combine_2[key](a, b) and val[key](a, b)
^^^^^^^^^^^^^^^  вызов самой себя   ^^^^^^^^^^^^^^^

Лямбда выполняется "лениво". Поэтому вы фактически делаете функцию, которая будет вызывать сама себя. funcs_combine_2 когда она наконец-то будет вызвана, будет вызывать сама себя до бесконечности.

Вы можете создать новый словарь и дальше работать уже с ним, чтобы не было рекурсии:

funcs_combine_result = {}
for val in funcs:
    for key in val.keys():
        funcs_combine_result[key] = lambda a, b: funcs_combine_2[key](a, b) and val[key](a, b)

print(funcs_combine_result['1'](3, 8))
print(funcs_combine_result['1'](1, 3))
print(funcs_combine_result['2'](6, 2))
print(funcs_combine_result['2'](5, 1))

Вывод:

False
False
True
True
→ Ссылка
Автор решения: Chorkov

Ваш вариант:

...
        funcs_combine_2[key] = lambda a, b: funcs_combine_2[key](a, b) and val[key](a, b)
...

здесь, в выражении lambda a, b: funcs_combine_2[key](a, b) and val[key](a, b) захватывается объект funcs_combine_2, а не текущее значение funcs_combine_2[key]. Учитывая, что вы модифицируете значение funcs_combine_2, при вызове возникнет бесконечная рекурсия, поскольку в ходе вычисления funcs_combine_2[key](a, b) нужно вычислить funcs_combine_2[key](a, b).

Решение: захватывать не funcs_combine_2, а текущее значение funcs_combine_2[key], для чего ему нужно присвоить отдельное имя (создать временную переменную).

funcs_combine_2 = {'1': lambda a, b: True, '2': lambda a, b: True}
for val in funcs:
    for key in val.keys():
        old_lambda = funcs_combine_2[key]
        new_lambda = val[key]
        funcs_combine_2[key] = lambda a, b: old_lambda(a, b) and new_lambda(a, b)

В общем случае, чтобы не захватывать ничего лишнего и не мусорить в глобальное пространство имен (old_lambda/new_lambda...), полезно ввести функцию для логического склеивания лямбд:

funcs = [{'1': lambda a, b: a + b > 5}, {'1': lambda a, b: b > a},
         {'2': lambda a, b: a * b > 10}, {'2': lambda a, b: a - b == 4}]


def combine_lambda(l1, l2):
    return lambda a, b: l1(a, b) and l2(a, b)

funcs_combine_2 = {}
for val in funcs:
    for key in val.keys():
        funcs_combine_2[key] = combine_lambda(funcs_combine_2.get(key, lambda a, b: True), val[key])

print(funcs_combine_2['1'](3, 8))
print(funcs_combine_2['1'](1, 3))
print(funcs_combine_2['2'](6, 2))
print(funcs_combine_2['2'](5, 1))
True
False
True
False
→ Ссылка