Условная производительность в закрытиях Python - PullRequest
2 голосов
/ 13 июня 2019

В приведенном ниже примере кода у меня есть две функции более высокого уровня, 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

1 Ответ

2 голосов
/ 13 июня 2019

В зависимости от того, что вы подразумеваете под «явным определением двух функций», учтите, что вам не нужно выполнять оператор 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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...