В приведенном ниже примере кода у меня есть две функции более высокого уровня, 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
@PatrickHaugh Я вижу, как это верно для factory1
в его текущем состоянии, но было бы неплохо, например, иметь какой-нибудь инструмент для оптимизации fn перед его возвратом, учитывая инвариантный характер condition
. Хотя не уверен, что такое существует.
Вы можете вычислить некоторое значение ret_val
в factory1
, а затем вернуть функцию, которая возвращает это значение. Однако для 99% случаев использования это невозможно. Обычно вы возвращаете функцию, потому что не можете предварительно вычислить значение, иначе вы бы просто вернули само значение.
Зависит ли различие исключительно от другого входы или оно является результатом отличающегося процесса или последовательности? Можно ли эффективно отделить общее от различия?
@PatrickHaugh В коде, над которым я работаю, я использую условное выражение для выбора между двумя связанными преобразованиями некоторых данных, которые функция должна обрабатывать, поэтому я думаю, что в конечном итоге я попаду в 99%
@wwii Я пытаюсь создать функцию, которая выглядит примерно так: python def function(data): if conditional: data = transform1(data) else: data = transform2(data) #More transforms on data return data
@wwii нелегко передать в форме комментария. Собираюсь редактировать Q.
В зависимости от того, что вы подразумеваете под «явным определением двух функций», обратите внимание, что вам не нужно выполнять оператор 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)
Как вкратце, но, возможно, недостаточно ясно обсуждалось в исходном посте: Проблема, которую я действительно пытаюсь избежать, — это дублирование кода. Представьте, что функция, которую мы определяем, намного сложнее, чем в этом примере, и что условное выражение — это только одна ее часть. Я хочу избежать выписывание и ведение двух почти идентичных копий функции. Так что ваше предложение, к сожалению, не избегает этого.
Мое обновление factory4
может удовлетворить ваши потребности, но вам нужно использовать его очень осторожно. Я не совсем уверен, что рекомендую это. factory5
безопаснее.
Идея factory4
действительно умная, но недостатком является то, что вы отказываетесь от преимуществ определения функции, которое может понять IDE, а также от удобочитаемости, поэтому не уверен, что это стоит затрат. Что касается factory5
: в коде, с которым я на самом деле работаю (возможно, стоит сделать это более понятным в моем примере), условное используется для выбора между двумя разными преобразованиями некоторых данных, поэтому я не могу зависеть от знания того, что должна делать функция. вернуть. Хотя идея все равно крутая.
Я думаю, что ваше предложение относительно моего редактирования кажется хорошим решением. Мне это не понравилось, потому что и transform1
, и transform2
на самом деле простые однострочники, которые я не сформулировал как функции, но, если подумать, кажется, что это единственный способ сделать это. Спасибо!
Если они достаточно просты, вы также можете передать анонимную функцию (используя лямбда-выражение) в качестве аргумента function
.
Возможно нет. Результат
factory2
быстрее, потому что ему не нужно проверятьcondition
, в то время как результатfactory1
проверяет. Эта проверка всегда будет занимать больше времени, чем ее отсутствие.