Можете ли вы динамически объединить несколько условных функций в одну в Python? - PullRequest
1 голос
/ 10 июня 2010

Мне любопытно, можно ли взять несколько условных функций и создать одну функцию, которая проверяет их все (например, способ, которым генератор берет процедуру для итерации ряда и создает итератор).

Основной вариант использования будет, когда у вас есть большое количество условных параметров (например, "max_a", "min_a", "max_b", "min_b" и т. Д.), Многие из которых могут быть пустыми. Все они будут переданы этой функции «создания функции», которая затем вернет одну функцию, которая проверила их все. Ниже приведен пример наивного способа выполнения того, что я спрашиваю:

def combining_function(max_a, min_a, max_b, min_b, ...):
    f_array = []
    if max_a is not None:
        f_array.append( lambda x: x.a < max_a )
    if min_a is not None:
        f_array.append( lambda x: x.a > min_a )
    ...

    return lambda x: all( [ f(x) for f in f_array ] )

Что мне интересно, так это то, что наиболее эффективно для достижения того, что делается выше? Кажется, что выполнение вызова функции для каждой функции в f_array создаст приличное количество накладных расходов, но, возможно, я участвую в преждевременной / ненужной оптимизации. В любом случае, мне было бы интересно узнать, сталкивался ли кто-нибудь еще с подобными случаями использования и как они проходили.

Кроме того, если это невозможно в Python, возможно ли это в других (возможно, более функциональных) языках?

РЕДАКТИРОВАТЬ: похоже, что консенсусное решение состоит в том, чтобы составить строку, содержащую полный набор условий, а затем использовать exec или eval для генерации одной функции. @doublep предполагает, что это довольно хакерски. Есть мысли о том, как это плохо? Можно ли при составлении функции проверять аргументы достаточно близко, чтобы подобное решение можно было считать безопасным? В конце концов, все, что требуется от строгой проверки, нужно выполнить только один раз, тогда как преимущество от более быстрого комбинированного условия может быть получено при большом количестве вызовов. Люди используют подобные вещи в сценариях развертывания, или это в основном техника, с которой можно поиграть?

Ответы [ 3 ]

1 голос
/ 10 июня 2010

Замена

return lambda x: all( [ f(x) for f in f_array ] )

на

return lambda x: all( f(x) for f in f_array )

даст более эффективный lambda, так как он остановится рано, если любой f вернет ложное значение и не нуждаетсясоздать ненужный список.Это возможно только на Python 2.4 или 2.5 и выше.Если вам нужно поддерживать древние ценности, сделайте следующее:

def check (x):
    for f in f_array:
        if not f (x):
            return False
    return True

return check

Наконец, если вам действительно нужно сделать это очень эффективным и не бояться хакерских решений,Вы можете попробовать компиляцию во время выполнения:

def combining_function (max_a, min_a):
    constants = { }
    checks    = []

    if max_a is not None:
        constants['max_a'] = max_a
        checks.append ('x.a < max_a')

    if min_a is not None:
        constants['min_a'] = min_a
        checks.append ('x.a > min_a')

    if not checks:
        return lambda x: True
    else:
        func = 'def check (x): return (%s)' % ') and ('.join (checks)
        exec func in constants, constants
        return constants['check']

class X:
    def __init__(self, a):
        self.a = a

check = combining_function (3, 1)
print check (X (0)), check (X (2)), check (X (4))

Обратите внимание, что в Python 3.x exec становится функцией, поэтому приведенный выше код не переносим.

1 голос
/ 10 июня 2010

Интерфейс combining_function() ужасен, но если вы не можете его изменить, вы можете использовать:

def combining_function(min_a, max_a, min_b, max_b):
    conditions = []
    for name, value in locals().items():
        if value is None:
            continue
        kind, sep, attr = name.partition("_")
        op = {"min": ">", "max": "<"}.get(kind, None)
        if op is None:
            continue
        conditions.append("x.%(attr)s %(op)s %(value)r" % dict(
            attr=attr, op=op, value=value))

    if conditions:
        return eval("lambda x: " + " and ".join(conditions), {})
    else:
        return lambda x: True
0 голосов
/ 10 июня 2010

Исходя из вашего примера, если ваш список возможных параметров представляет собой последовательность max,min,max,min,max,min,..., то вот простой способ сделать это:

def combining_function(*args):
    maxs, mins = zip(*zip(*[iter(args)]*2))
    minv = max(m for m in mins if m is not None)
    maxv = min(m for m in maxs if m is not None)
    return lambda x: minv < x.a < maxv

Но этот вид немного «обманывает»: он предварительно вычисляет наименьшее максимальное значение и наибольшее минимальное значение. Если ваши тесты могут быть чем-то более сложным, чем просто макс / мин тестирование, код нужно будет изменить.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...