Связывание условных функций в Python

#python #function #chaining

#python #функция #связывание

Вопрос:

Представьте, что есть функция, g которую я хочу реализовать, связав подфункции. Это можно легко сделать с помощью:

 def f1(a):
    return a 1

def f2(a):
    return a*2

def f3(a):
    return a**3

g = lambda x: f1(f2(f3(x)))
  

Однако теперь рассмотрим, какие подфункции будут объединены в цепочку, зависит от условий: в частности, от заданных пользователем параметров, которые известны заранее. Можно было бы, конечно, сделать:

 def g(a, cond1, cond2, cond3):

    res = a
    if cond1:
        res = f3(res)
    if cond2:
        res = f2(res)
    if cond3:
        res = f1(res)
    return res
  

Однако, вместо динамической проверки этих статических условий при каждом вызове функции, я предполагаю, что лучше заранее определить функцию g на основе составляющих ее функций.
К сожалению, следующее дает RuntimeError: maximum recursion depth exceeded :

 g = lambda x: x
if cond1:
    g = lambda x: f3(g(x))
if cond2:
    g = lambda x: f2(g(x))
if cond3:
    g = lambda x: f1(g(x))
  

Есть ли хороший способ выполнить это условное связывание в Python? Пожалуйста, обратите внимание, что функций, которые будут объединены в цепочку, может быть N, поэтому невозможно отдельно определять все 2 ^ N комбинаций функций (8 в этом примере).

Комментарии:

1. g = lambda x: f(g(x)) взорвет ваш стек, потому что конечный вызов никогда не заканчивается.

2. как вы намереваетесь определить эти N функций? Все они будут называться функциями, так что f1, f2, ...fN или вы поместите в dict или что-то вроде? Я спрашиваю, потому что это в значительной степени определит, как их эффективно связать.

3. Можно предположить, что все они являются именованными функциями.

4. @RockyLi Да, в принципе, я хочу избежать рекурсии и вместо этого использовать g в качестве объекта функции, определенного ранее в коде

5. Это можно было бы заставить работать (не говоря уже о том, что это обязательно хорошая идея), записав ваши условные обозначения как g = lambda x, g=g: f3(g(x)) (параметр по умолчанию фиксирует предыдущее значение g , а не рекурсивно ссылается на новое значение).

Ответ №1:

Я нашел одно решение с использованием декораторов. Взгляните:

 def f1(x):
    return x   1

def f2(x):
    return x   2

def f3(x):
    return x ** 2


conditions = [True, False, True]
functions = [f1, f2, f3]


def apply_one(func, function):
    def wrapped(x):
        return func(function(x))
    return wrapped


def apply_conditions_and_functions(conditions, functions):
    def initial(x):
        return x

    function = initial

    for cond, func in zip(conditions, reversed(functions)):
        if cond:
            function = apply_one(func, function)
    return function


g = apply_conditions_and_functions(conditions, functions)

print(g(10)) # 101, because f1(f3(10)) = (10 ** 2)   1 = 101
  

Условия проверяются только один раз при определении g функции, они не проверяются при ее вызове.

Ответ №2:

Наиболее структурно похожий код, который я могу придумать, должен быть структурирован следующим образом, вашим f1.. f3 нужно будет стать псевдодекораторами, вот так:

 def f1(a):
    def wrapper(*args):
        return a(*args) 1
    return wrapper

def f2(a):
    def wrapper(*args):
        return a(*args)*2
    return wrapper

def f3(a):
    def wrapper(*args):
        return a(*args)**3
    return wrapper
  

И затем вы можете применить это к каждой функции.

 g = lambda x: x
if cond1:
    g = f3(g)
if cond2:
    g = f2(g)
if cond3:
    g = f1(g)
g(2)
  

ВОЗВРАТ:

 # Assume cond1..3 are all True
17 # (2**3*2 1)
  

Комментарии:

1. Благодарим вас за это решение. Это в основном то же самое, что и решение Sanyash, с использованием декораторов. Ваш вопрос структурно наиболее похож на вопрос, как вы упомянули, в то время как его использует дополнительный уровень абстракции, который позволяет определениям f1 fN оставаться чистыми. В конце концов я принял его ответ, потому что он был на 5′ быстрее. Я не очень опытен в stackoverflow, поэтому дайте мне знать, если вы считаете, что я должен был выбрать ваш вместо этого.

2. Не беспокойтесь о выборе — ответ призван помочь, а не подсчитать очки. Я рад, что его ответ сработал для вас.