J'ai un tableau de fonctions et j'essaie de produire une fonction qui consiste à composer les éléments de mon tableau. Mon approche est la suivante:
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
Cette méthode ne semble pas fonctionner, une aide sera appréciée.
(J'inverse la liste parce que c'est l'ordre de composition que je veux que les fonctions soient)
Cela ne fonctionne pas car toutes les fonctions anonymes que vous créez dans la boucle font référence à la même variable de boucle et partagent donc sa valeur finale.
Pour remédier rapidement à ce problème, vous pouvez remplacer l’affectation par:
final = lambda x, f=f, final=final: f(final(x))
Ou, vous pouvez retourner le lambda à partir d'une fonction:
def wrap(accum, f):
return lambda x: f(accum(x))
...
final = wrap(final, f)
Pour comprendre ce qui se passe, essayez cette expérience:
>>> l = [lambda: n for n in xrange(10)]
>>> [f() for f in l]
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9]
Ce résultat surprend de nombreuses personnes, qui s’attendent à ce que le résultat soit [0, 1, 2, ...]
. Cependant, tous les lambdas pointent vers la même variable n
et font tous référence à sa valeur finale, qui est 9. Dans votre cas, toutes les versions de final
qui sont supposées imbriquer finissent par se référer à la même f
et, pire encore, à la même final
.
Le sujet de lambdas et des boucles for en Python a été déjà couvert sur SO .
L’approche la plus simple serait d’abord d’écrire une composition de 2 fonctions:
def compose2(f, g):
return lambda *a, **kw: f(g(*a, **kw))
Et utilisez ensuite reduce
pour composer plus de fonctions:
def compose(*fs):
return reduce(compose2, fs)
Ou vous pouvez utiliser une bibliothèque , qui contient déjà la fonction compose .
def compose (*functions):
def inner(arg):
for f in reversed(functions):
arg = f(arg)
return arg
return inner
Exemple:
>>> 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
Voici une implémentation récursive assez élégante, qui utilise les fonctionnalités de Python 3 pour plus de clarté:
def strict_compose(*funcs):
*funcs, penultimate, last = funcs
if funcs:
penultimate = strict_compose(*funcs, penultimate)
return lambda *args, **kwargs: penultimate(last(*args, **kwargs))
Version compatible 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))
Ceci est une version antérieure qui utilise une évaluation paresseuse de la récursivité:
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
Les deux semblent faire un nouveau tuple et dict d'arguments chaque appel récursif.
Testons certaines de ces implémentations et déterminons laquelle est la plus performante, en premier lieu quelques fonctions à un seul argument (Merci poke):
def square(x):
return x ** 2
def increment(x):
return x + 1
def half(x):
return x / 2
Voici nos implémentations, je soupçonne que ma version itérative est la deuxième plus efficace (la composition manuelle sera naturellement la plus rapide), mais cela peut être dû en partie au fait qu'elle évite la difficulté de passer un certain nombre d'arguments ou d'arguments de mots-clés entre les fonctions - dans la plupart des cas nous ne verrons que le seul argument trivial transmis.
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)
Et pour tester ceux-ci:
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))
Et nous obtenons le résultat suivant (même magnitude et proportion en Python 2 et 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
Et mes attentes ont été confirmées: le plus rapide est bien sûr la composition de fonctions manuelle suivie de la mise en œuvre itérative. La version récursive paresseuse est beaucoup plus lente, ce qui est probablement dû au fait qu'un nouveau cadre de pile est créé par chaque appel de fonction et qu'un nouveau Tuple de fonctions est créé pour chaque fonction.
Pour une comparaison meilleure et peut-être plus réaliste, si vous supprimez **kwargs
et remplacez *args
par arg
dans les fonctions, celles qui les utilisent seront plus performantes et nous pourrons mieux comparer des pommes avec des pommes. Ici, en dehors de la composition manuelle, reduction_compose1 gagne suivi du 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
Fonctionne avec un seul argument:
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)
Bon mot:
compose = lambda *F: reduce(lambda f, g: lambda x: f(g(x)), F)
Exemple d'utilisation:
f1 = lambda x: x+3
f2 = lambda x: x*2
f3 = lambda x: x-1
g = compose(f1, f2, f3)
assert(g(7) == 15)
Vous pouvez également créer un tableau de fonctions et utiliser
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)
La réponse de Poke est bonne, mais vous pouvez également utiliser le package functional
qui est fourni avec une méthode de composition.
pip install funcoperators
est une autre bibliothèque à implémenter qui permet la notation infixe:
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)
pip install funcoperators https://pypi.org/project/funcoperators/
Disclaimer: je suis le créateur du module
L'implémentation la plus fiable que j'ai trouvée se trouve dans la bibliothèque tierce partie toolz
. La fonction compose
de cette bibliothèque traite également docstring pour la composition des fonctions.
Le code source est disponible gratuitement. Vous trouverez ci-dessous un exemple simple d'utilisation.
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
Ceci est ma version
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
Un exemple d'utilisation
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 __== '__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))