Условная производительность в замыканиях Python

В приведенном ниже примере кода у меня есть две функции более высокого уровня, factory1 и factory2, которые создают функцию с идентичным поведением. Первая фабрика, factory1, позволяет избежать явного определения двух разных функций, позволяя возвращаемой функции изменять поведение на основе логического значения из фабрики. Полезность этого не так очевидна в этом примере, но если бы создаваемая функция была более сложной, было бы вредно как для удобочитаемости, так и для удобства сопровождения, чтобы явно записать две почти идентичные копии функции, как это сделано в factory2 .

Однако реализация factory2 работает быстрее, что видно по результатам тайминга.

Есть ли способ добиться производительности factory2 без явного определения двух альтернативных функций?

def factory1(condition):

    def fn():
        if condition:
            return "foo"
        else:
            return "bar"

    return fn


def factory2(condition):

    def foo_fn():
        return "foo"

    def bar_fn():
        return "bar"

    if condition:
        return foo_fn
    else:
        return bar_fn


def test1():
    fn = factory1(True)
    for _ in range(1000):
        fn()


def test2():
    fn = factory2(True)
    for _ in range(1000):
        fn()


if __name__ == '__main__':
    import timeit
    print(timeit.timeit("test1()", setup = "from __main__ import test1"))
    # >>> 62.458039999
    print(timeit.timeit("test2()", setup = "from __main__ import test2"))
    # >>> 49.203676939

Обновлено: еще немного контекста

Причина, по которой я спрашиваю, заключается в том, что я пытаюсь создать функцию, которая выглядит примерно так:

def function(data):
    data = some_transform(data)

    if condition:
        # condition should be considered invariant at time of definition
        data = transform1(data)
    else:
        data = transform2(data)

    data = yet_another_transform(data)

    return data

Возможно нет. Результат factory2 быстрее, потому что ему не нужно проверять condition, в то время как результат factory1 проверяет. Эта проверка всегда будет занимать больше времени, чем ее отсутствие.

Patrick Haugh 13.06.2019 15:55

@PatrickHaugh Я вижу, как это верно для factory1 в его текущем состоянии, но было бы неплохо, например, иметь какой-нибудь инструмент для оптимизации fn перед его возвратом, учитывая инвариантный характер condition. Хотя не уверен, что такое существует.

Harald Husum 13.06.2019 16:04

Вы можете вычислить некоторое значение ret_val в factory1, а затем вернуть функцию, которая возвращает это значение. Однако для 99% случаев использования это невозможно. Обычно вы возвращаете функцию, потому что не можете предварительно вычислить значение, иначе вы бы просто вернули само значение.

Patrick Haugh 13.06.2019 16:16

Зависит ли различие исключительно от другого входы или оно является результатом отличающегося процесса или последовательности? Можно ли эффективно отделить общее от различия?

wwii 13.06.2019 16:42

@PatrickHaugh В коде, над которым я работаю, я использую условное выражение для выбора между двумя связанными преобразованиями некоторых данных, которые функция должна обрабатывать, поэтому я думаю, что в конечном итоге я попаду в 99%

Harald Husum 13.06.2019 23:48

@wwii Я пытаюсь создать функцию, которая выглядит примерно так: python def function(data): if conditional: data = transform1(data) else: data = transform2(data) #More transforms on data return data

Harald Husum 13.06.2019 23:49

@wwii нелегко передать в форме комментария. Собираюсь редактировать Q.

Harald Husum 13.06.2019 23:54
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
7
150
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В зависимости от того, что вы подразумеваете под «явным определением двух функций», обратите внимание, что вам не нужно выполнять оператор def, пока вы не проверите условие:

def factory3(condition):

    if condition:
        def fn():
            return "foo"
    else:
        def fn():
            return "bar"

    return fn

Кто-то может возразить, что для этого по-прежнему необходимо скомпилировать два объекта кода, прежде чем определить, какой из них будет использоваться для определения функции во время выполнения. В этом случае вы можете отказаться от использования exec в динамически созданной строке. ПРИМЕЧАНИЕ Это нужно делать осторожно для всего, кроме статического примера, который я покажу здесь. См. старое определение для namedtuple для хорошего (?) примера.

def factory4(condition):
    code = """def fn():\n    return "{}"\n""".format("foo" if condition else "bar")
    exec(code)
    return fn

Более безопасной альтернативой может быть использование замыкания:

def factory5(condition):
    def make_fun(val):
        def _():
            return val
        return _
    if condition:
        return make_fun("foo")
    else:
        return make_fun("bar")

make_fun также может быть определен вне factory5, так как он вообще не зависит от condition.


Основываясь на вашем редактировании, я думаю, вы просто хотите реализовать внедрение зависимостей. Не помещайте оператор if внутрь своей функции; передать transform1 или transform2 в качестве аргумента:

def function(transform):
    def _(data):
        data = some_transform(data)
        data = transform(data)
        data = yet_another_transform(data)
        return data
    return _

if condition:
    thing = function(transform1)
else:
    thing = function(transform2)

Как вкратце, но, возможно, недостаточно ясно обсуждалось в исходном посте: Проблема, которую я действительно пытаюсь избежать, — это дублирование кода. Представьте, что функция, которую мы определяем, намного сложнее, чем в этом примере, и что условное выражение — это только одна ее часть. Я хочу избежать выписывание и ведение двух почти идентичных копий функции. Так что ваше предложение, к сожалению, не избегает этого.

Harald Husum 13.06.2019 16:21

Мое обновление factory4 может удовлетворить ваши потребности, но вам нужно использовать его очень осторожно. Я не совсем уверен, что рекомендую это. factory5 безопаснее.

chepner 13.06.2019 16:26

Идея factory4 действительно умная, но недостатком является то, что вы отказываетесь от преимуществ определения функции, которое может понять IDE, а также от удобочитаемости, поэтому не уверен, что это стоит затрат. Что касается factory5: в коде, с которым я на самом деле работаю (возможно, стоит сделать это более понятным в моем примере), условное используется для выбора между двумя разными преобразованиями некоторых данных, поэтому я не могу зависеть от знания того, что должна делать функция. вернуть. Хотя идея все равно крутая.

Harald Husum 13.06.2019 23:45

Я думаю, что ваше предложение относительно моего редактирования кажется хорошим решением. Мне это не понравилось, потому что и transform1, и transform2 на самом деле простые однострочники, которые я не сформулировал как функции, но, если подумать, кажется, что это единственный способ сделать это. Спасибо!

Harald Husum 17.06.2019 11:09

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

chepner 17.06.2019 15:19

Другие вопросы по теме