Составляющие функции в python
У меня есть массив функций, и я пытаюсь создать одну функцию, состоящую из состава элементов в моем массиве.
Мой подход:
def compose(list):
if len(list) == 1:
return lambda x:list[0](x)
list.reverse()
final=lambda x:x
for f in list:
final=lambda x:f(final(x))
return final
Этот метод, похоже, не работает, помощь будет оценена.
(Я меняю список, потому что это порядок композиции, в котором я хочу, чтобы функции были)
Ответы
Ответ 1
Это не работает, потому что все анонимные функции, которые вы создаете в цикле, ссылаются на одну и ту же переменную цикла и, следовательно, разделяют ее конечное значение.
В качестве быстрого исправления вы можете заменить назначение:
final = lambda x, f=f, final=final: f(final(x))
Или вы можете вернуть лямбду из функции:
def wrap(accum, f):
return lambda x: f(accum(x))
...
final = wrap(final, f)
Чтобы понять, что происходит, попробуйте этот эксперимент:
>>> l = [lambda: n for n in xrange(10)]
>>> [f() for f in l]
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9]
Этот результат удивляет многих людей, ожидающих, что результатом будет [0, 1, 2, ...]
. Тем не менее, все лямбда указывают на ту же переменную n
, и все относятся к ее окончательному значению, которое равно 9. В вашем случае все версии final
, которые должны входить в конец, ссылаются на тот же f
и, что еще хуже, к тому же final
.
Тема lambdas и для циклов в Python уже уже рассмотрена в SO.
Ответ 2
Самый простой способ - сначала написать композицию из двух функций:
def compose2(f, g):
return lambda *a, **kw: f(g(*a, **kw))
И затем используйте reduce
для создания дополнительных функций:
def compose(*fs):
return reduce(compose2, fs)
Или вы можете использовать некоторую библиотеку, которая уже содержит compose.
Ответ 3
def compose (*functions):
def inner(arg):
for f in reversed(functions):
arg = f(arg)
return arg
return inner
Пример:
>>> def square (x):
return x ** 2
>>> def increment (x):
return x + 1
>>> def half (x):
return x / 2
>>> composed = compose(square, increment, half) # square(increment(half(x)))
>>> composed(5) # square(increment(half(5))) = square(increment(2.5)) = square(3.5) = 12,25
12.25
Ответ 4
Рекурсивная реализация
Вот довольно элегантная рекурсивная реализация, которая для ясности использует возможности Python 3:
def strict_compose(*funcs):
*funcs, penultimate, last = funcs
if funcs:
penultimate = strict_compose(*funcs, penultimate)
return lambda *args, **kwargs: penultimate(last(*args, **kwargs))
Python 2 совместимая версия:
def strict_compose2(*funcs):
if len(funcs) > 2:
penultimate = strict_compose2(*funcs[:-1])
else:
penultimate = funcs[-2]
return lambda *args, **kwargs: penultimate(funcs[-1](*args, **kwargs))
Это более ранняя версия, которая использует ленивую оценку рекурсии:
def lazy_recursive_compose(*funcs):
def inner(*args, _funcs=funcs, **kwargs):
if len(_funcs) > 1:
return inner(_funcs[-1](*args, **kwargs), _funcs=_funcs[:-1])
else:
return _funcs[0](*args, **kwargs)
return inner
Кажется, что оба делают новый кортеж и диктовку аргументов при каждом рекурсивном вызове.
Сравнение всех предложений:
Давайте протестируем некоторые из этих реализаций и определим, какие из них наиболее производительны, сначала некоторые функции с одним аргументом (спасибо, poke):
def square(x):
return x ** 2
def increment(x):
return x + 1
def half(x):
return x / 2
Здесь наши реализации, я подозреваю, что моя итеративная версия является второй наиболее эффективной (ручная компоновка, естественно, будет самой быстрой), но это может быть отчасти из-за того, что она обходит сложность передачи любого количества аргументов или аргументов ключевого слова между функциями - в большинстве случаев мы увидим только тривиальный аргумент.
from functools import reduce
def strict_recursive_compose(*funcs):
*funcs, penultimate, last = funcs
if funcs:
penultimate = strict_recursive_compose(*funcs, penultimate)
return lambda *args, **kwargs: penultimate(last(*args, **kwargs))
def strict_recursive_compose2(*funcs):
if len(funcs) > 2:
penultimate = strict_recursive_compose2(*funcs[:-1])
else:
penultimate = funcs[-2]
return lambda *args, **kwargs: penultimate(funcs[-1](*args, **kwargs))
def lazy_recursive_compose(*funcs):
def inner(*args, _funcs=funcs, **kwargs):
if len(_funcs) > 1:
return inner(_funcs[-1](*args, **kwargs), _funcs=_funcs[:-1])
else:
return _funcs[0](*args, **kwargs)
return inner
def iterative_compose(*functions):
"""my implementation, only accepts one argument."""
def inner(arg):
for f in reversed(functions):
arg = f(arg)
return arg
return inner
def _compose2(f, g):
return lambda *a, **kw: f(g(*a, **kw))
def reduce_compose1(*fs):
return reduce(_compose2, fs)
def reduce_compose2(*funcs):
"""bug fixed - added reversed()"""
return lambda x: reduce(lambda acc, f: f(acc), reversed(funcs), x)
И чтобы проверить это:
import timeit
def manual_compose(n):
return square(increment(half(n)))
composes = (strict_recursive_compose, strict_recursive_compose2,
lazy_recursive_compose, iterative_compose,
reduce_compose1, reduce_compose2)
print('manual compose', min(timeit.repeat(lambda: manual_compose(5))), manual_compose(5))
for compose in composes:
fn = compose(square, increment, half)
result = min(timeit.repeat(lambda: fn(5)))
print(compose.__name__, result, fn(5))
Результаты
И мы получаем следующий результат (одинаковые величины и пропорции в Python 2 и 3):
manual compose 0.4963762479601428 12.25
strict_recursive_compose 0.6564744340721518 12.25
strict_recursive_compose2 0.7216697579715401 12.25
lazy_recursive_compose 1.260614730999805 12.25
iterative_compose 0.614982972969301 12.25
reduce_compose1 0.6768529079854488 12.25
reduce_compose2 0.9890829260693863 12.25
И мои ожидания подтвердились: конечно, самое быстрое - это ручная композиция функций с последующей итеративной реализацией. Ленивая рекурсивная версия намного медленнее - вероятно, так как новый кадр стека создается при каждом вызове функции, а новый набор функций создается для каждой функции.
Для лучшего и, возможно, более реалистичного сравнения, если вы удалите **kwargs
и измените *args
на arg
в функциях, те, которые их используют, будут более производительными, и мы можем лучше сравнить яблоки с яблоками - здесь, кроме ручной композиции, Reduce_compose1 выигрывает, а затем strict_recursive_compose:
manual compose 0.443808660027571 12.25
strict_recursive_compose 0.5409777010791004 12.25
strict_recursive_compose2 0.5698030130006373 12.25
lazy_recursive_compose 1.0381018499610946 12.25
iterative_compose 0.619289995986037 12.25
reduce_compose1 0.49532539502251893 12.25
reduce_compose2 0.9633988010464236 12.25
Функции только с одним аргументом:
def strict_recursive_compose(*funcs):
*funcs, penultimate, last = funcs
if funcs:
penultimate = strict_recursive_compose(*funcs, penultimate)
return lambda arg: penultimate(last(arg))
def strict_recursive_compose2(*funcs):
if len(funcs) > 2:
penultimate = strict_recursive_compose2(*funcs[:-1])
else:
penultimate = funcs[-2]
return lambda arg: penultimate(funcs[-1](arg))
def lazy_recursive_compose(*funcs):
def inner(arg, _funcs=funcs):
if len(_funcs) > 1:
return inner(_funcs[-1](arg), _funcs=_funcs[:-1])
else:
return _funcs[0](arg)
return inner
def iterative_compose(*functions):
"""my implementation, only accepts one argument."""
def inner(arg):
for f in reversed(functions):
arg = f(arg)
return arg
return inner
def _compose2(f, g):
return lambda arg: f(g(arg))
def reduce_compose1(*fs):
return reduce(_compose2, fs)
def reduce_compose2(*funcs):
"""bug fixed - added reversed()"""
return lambda x: reduce(lambda acc, f: f(acc), reversed(funcs), x)
Ответ 5
Один вкладыш:
compose = lambda *F: reduce(lambda f, g: lambda x: f(g(x)), F)
Пример использования:
f1 = lambda x: x+3
f2 = lambda x: x*2
f3 = lambda x: x-1
g = compose(f1, f2, f3)
assert(g(7) == 15)
Ответ 6
Вы также можете создать массив функций и использовать сокращение:
def f1(x): return x+1
def f2(x): return x+2
def f3(x): return x+3
x = 5
# Will print f3(f2(f1(x)))
print reduce(lambda acc, x: x(acc), [f1, f2, f3], x)
# As a function:
def compose(*funcs):
return lambda x: reduce(lambda acc, f: f(acc), funcs, x)
f = compose(f1, f2, f3)
Ответ 7
Ответ Poke хороший, но вы также можете использовать пакет functional
, который поставляется вместе с методом компоновки.
Ответ 8
pip install funcoperators
- еще одна библиотека для ее реализации, которая позволяет использовать инфиксную нотацию:
from funcoperators import compose
# display = lambda x: hex(ord(list(x)))
display = hex *compose* ord *compose* list
# also works as a function
display = compose(hex, ord, list)
funpperators pip install https://pypi.org/project/funcoperators/
Отказ от ответственности: я создатель модуля
Ответ 9
Самая надежная реализация, которую я нашел, находится в стороннем toolz
библиотеки. Функция compose
из этой библиотеки также имеет дело с строкой документации для композиции функций.
Исходный код находится в свободном доступе. Ниже приведен простой пример использования.
from toolz import compose
def f(x):
return x+1
def g(x):
return x*2
def h(x):
return x+3
res = compose(f, g, h)(5) # 17
Ответ 10
Это моя версия
def compose(*fargs):
def inner(arg):
if not arg:
raise ValueError("Invalid argument")
if not all([callable(f) for f in fargs]):
raise TypeError("Function is not callable")
return reduce(lambda arg, func: func(arg), fargs, arg)
return inner
Пример использования
def calcMean(iterable):
return sum(iterable) / len(iterable)
def formatMean(mean):
return round(float(mean), 2)
def adder(val, value):
return val + value
def isEven(val):
return val % 2 == 0
if __name__ == '__main__':
# Ex1
rand_range = [random.randint(0, 10000) for x in range(0, 10000)]
isRandIntEven = compose(calcMean, formatMean,
partial(adder, value=0), math.floor.__call__, isEven)
print(isRandIntEven(rand_range))
Ответ 11
Более общее решение Imanol Luengo с моей точки зрения (пример Python Notebook):
from functools import reduce
from functools import partial
def f(*argv, **kwargs):
print('f: {} {}'.format(argv, kwargs))
return argv, kwargs
def g(*argv, **kwargs):
print('g: {} {}'.format(argv, kwargs))
return argv, kwargs
def compose(fs, *argv, **kwargs):
return reduce(lambda x, y: y(*x[0], **x[1]), fs, (argv, kwargs))
h = partial(compose, [f, g])
h('value', key='value')
output:
f: ('value',) {'key': 'value'}
g: ('value',) {'key': 'value'}
m = partial(compose, [h, f, g])
m('value', key='value')
output:
f: ('value',) {'key': 'value'}
g: ('value',) {'key': 'value'}
f: ('value',) {'key': 'value'}
g: ('value',) {'key': 'value'}