Currying Python et partiel
En faisant des exercices de programmation sur codewars.com, j'ai rencontré un exercice sur le curry et les fonctions partielles.
Étant novice en programmation et nouveau sur le sujet, j'ai cherché sur Internet des informations sur le sujet et je suis allé assez loin dans la résolution de l'exercice. Cependant, je suis maintenant tombé sur un obstacle que je n'arrive pas à surmonter et je cherche ici un coup de pouce dans la bonne direction.
L'exercice est assez simple: écrire une fonction qui peut curry et / ou partiel toute fonction d'entrée et évalue la fonction d'entrée une fois que suffisamment de paramètres d'entrée sont fournis. La fonction d'entrée peut accepter n'importe quel nombre de paramètres d'entrée. De plus, la fonction curry / partial devrait être très flexible dans la façon dont elle est appelée, étant capable de gérer de très nombreuses façons d'appeler la fonction. De plus, la fonction curry / partial peut être appelée avec plus d'entrées que ce qui est requis par la fonction d'entrée, dans ce cas, toutes les entrées en excès doivent être ignorées.
En suivant le lien exercice, vous trouverez tous les cas de test que la fonction doit pouvoir gérer.
Le code que j'ai trouvé est le suivant:
from functools import partial
from inspect import signature
def curry_partial(func, *initial_args):
""" Generates a 'curried' version of a function. """
# Process any initial arguments that where given. If the number of arguments that are given exceeds
# minArgs (the number of input arguments that func needs), func is evaluated
minArgs = len(signature(func).parameters)
if initial_args:
if len(initial_args) >= minArgs:
return func(*initial_args[:minArgs])
func = partial(func, *initial_args)
minArgs = len(signature(func).parameters)
# Do the currying
def g(*myArgs):
nonlocal minArgs
# Evaluate function if we have the necessary amount of input arguments
if minArgs is not None and minArgs <= len(myArgs):
return func(*myArgs[:minArgs])
def f(*args):
nonlocal minArgs
newArgs = myArgs + args if args else myArgs
if minArgs is not None and minArgs <= len(newArgs):
return func(*newArgs[:minArgs])
else:
return g(*newArgs)
return f
return g
Maintenant, ce code échoue lorsque le test suivant est exécuté:
test.assert_equals(curry_partial(curry_partial(curry_partial(add, a), b), c), sum)
où add = a + b + c (fonction correctement définie), a = 1, b = 2, c = 3 et sum = 6.
La raison pour laquelle cela échoue est parce que curry_partial(add, a)
renvoie un descripteur de fonction à la fonction g
. Dans le deuxième appel, curry_partial(<function_handle to g>, b)
le calcul minArgs = len(signature(func).parameters)
ne fonctionne pas comme je le souhaite, car il calculera maintenant le nombre d'arguments d'entrée requis par la fonction g
(ce qui est 1
: c'est-à-dire *myArgs
), et non le nombre d'arguments requis par l'original func
. La question est donc de savoir comment écrire mon code de manière à pouvoir suivre le nombre d'arguments d'entrée dont mon original a func
encore besoin (en réduisant ce nombre chaque fois que je partitionne la fonction avec des arguments initiaux donnés).
J'ai encore beaucoup à apprendre sur la programmation et le curry / partiel, donc je n'ai probablement pas choisi l'approche la plus pratique. Mais j'aimerais apprendre. La difficulté dans cet exercice pour moi est la combinaison de partial et curry, c'est-à-dire faire une boucle de curry tout en partitionnant les arguments initiaux rencontrés.
Réponses
Essayez ceci.
from inspect import signature
# Here `is_set` acts like a flip-flop
is_set = False
params = 0
def curry_partial(func, *partial_args):
"""
Required argument: func
Optional argument: partial_args
Return:
1) Result of the `func` if
`partial_args` contains
required number of items.
2) Function `wrapper` if `partial_args`
contains less than the required
number of items.
"""
global is_set, params
if not is_set:
is_set = True
# if func is already a value
# we should return it
try: params = len(signature(func).parameters)
except: return func
try:
is_set = False
return func(*partial_args[:params])
except:
is_set = True
def wrapper(*extra_args):
"""
Optional argument: extra_args
Return:
1) Result of the `func` if `args`
contains required number of
items.
2) Result of `curry_partial` if
`args` contains less than the
required number of items.
"""
args = (partial_args + extra_args)
try:
is_set = False
return func(*args[:params])
except:
is_set = True
return curry_partial(func, *args)
return wrapper
Ce n'est en effet pas très bon de par sa conception. Au lieu de cela, vous devriez utiliser class
, pour faire tous les travaux internes comme, par exemple, la bascule (ne vous inquiétez pas, nous n'avons pas besoin de bascule ;-)).
Chaque fois qu'une fonction prend des arguments arbitraires, vous pouvez toujours instancier cette classe en passant la fonction. Mais cette fois cependant, je vous laisse cela.
Je ne suis pas sûr du curry , mais si vous avez besoin d'un simple générateur de fonctions partielles, vous pouvez essayer quelque chose comme ceci:
from functools import partial
from inspect import signature
def execute_or_partial(f, *args):
max = len(signature(f).parameters)
if len(args) >= max:
return f(*args[:max])
else:
return partial(f, *args)
s = lambda x, y, z: x + y + z
t = execute_or_partial(s, 1)
u = execute_or_partial(t, 2)
v = execute_or_partial(u, 3)
print(v)
or
print(execute_or_partial(execute_or_partial(execute_or_partial(s, 1), 2), 3))
Même si cela ne résout pas votre problème d'origine, voyez si vous pouvez utiliser le code ci-dessus pour réduire la répétition du code (je ne suis pas sûr, mais je pense qu'il y a une certaine répétition de code dans la fonction interne?); cela facilitera la résolution des problèmes ultérieurs.
Il se peut que certaines fonctions de la bibliothèque standard résolvent déjà ce problème. De nombreux langages fonctionnels purs comme Haskell ont cette fonctionnalité intégrée dans le langage.