Sur codewars.com, j'ai rencontré la tâche suivante:
Créez une fonction
add
qui ajoute des nombres lorsqu’elle est appelée successivement. Donc,add(1)
devrait renvoyer1
,add(1)(2)
devrait renvoyer1+2
, ...
Bien que je connaisse les bases de Python, je n’ai jamais rencontré de fonction pouvant être appelée dans une telle succession, c’est-à-dire une fonction f(x)
pouvant être appelée f(x)(y)(z)...
. Jusqu'ici, je ne sais même pas comment interpréter cette notation.
En tant que mathématicien, je soupçonne que f(x)(y)
est une fonction qui attribue à chaque x
une fonction g_{x}
, puis renvoie g_{x}(y)
et de même pour f(x)(y)(z)
.
Si cette interprétation était correcte, Python me permettrait de créer dynamiquement des fonctions qui me semblent très intéressantes. J'ai cherché sur le Web pendant une heure, mais je n'ai pas réussi à trouver une piste dans la bonne direction. Puisque je ne sais pas comment on appelle ce concept de programmation, cela n’est peut-être pas trop surprenant.
Comment appelez-vous ce concept et où puis-je en savoir plus à ce sujet?
Je ne sais pas s'il s'agit de fonction chaînant autant que de appelable chaînant, mais, comme les fonctions sont callables, je suppose qu'il n'y a pas de mal à faire. De toute façon, je peux penser à cela de deux manières:
int
et définissant __call__
:La première méthode consisterait en une sous-classe int
personnalisée qui définit __call__
qui renvoie une nouvelle instance avec la valeur mise à jour:
class CustomInt(int):
def __call__(self, v):
return CustomInt(self + v)
La fonction add
peut maintenant être définie pour renvoyer une instance CustomInt
qui, en tant qu'appelable renvoyant une valeur mise à jour d'elle-même, peut être appelée successivement:
>>> def add(v):
... return CustomInt(v)
>>> add(1)
1
>>> add(1)(2)
3
>>> add(1)(2)(3)(44) # and so on..
50
De plus, en tant que sous-classe int
, la valeur renvoyée conserve le comportement __repr__
et __str__
de int
s. Pour les opérations plus complexes, vous devez définir les autres dunders de manière appropriée}.
Comme @Caridorc l'a noté dans un commentaire, add
pourrait également être simplement écrit ainsi:
add = CustomInt
Renommer la classe en add
au lieu de CustomInt
fonctionne également de la même manière.
Le seul autre moyen auquel je peux penser implique une fonction imbriquée qui nécessite un appel d'argument vide supplémentaire afin de renvoyer le résultat. J'utilise pas avec nonlocal
et j'opte pour l'attachement d'attributs aux objets fonction pour le rendre portable entre Pythons:
def add(v):
def _inner_adder(val=None):
"""
if val is None we return _inner_adder.v
else we increment and return ourselves
"""
if val is None:
return _inner_adder.v
_inner_adder.v += val
return _inner_adder
_inner_adder.v = v # save value
return _inner_adder
Ceci retourne continuellement lui-même (_inner_adder
) qui, si une val
est fournie, l'incrémente (_inner_adder += val
) et sinon, renvoie la valeur telle quelle. Comme je l'ai mentionné, il faut un appel supplémentaire ()
pour renvoyer la valeur incrémentée:
>>> add(1)(2)()
3
>>> add(1)(2)(3)() # and so on..
6
Vous pouvez me détester, mais voici un one-liner :)
add = lambda v: type("", (int,), {"__call__": lambda self, v: self.__class__(self + v)})(v)
Edit: Ok, comment ça marche? Le code est identique à la réponse de @Jim, mais tout se passe sur une seule ligne.
type
peut être utilisé pour construire de nouveaux types: type(name, bases, dict) -> a new type
. Pour name
, nous fournissons une chaîne vide, car le nom n'est pas vraiment nécessaire dans ce cas. Pour bases
(Tuple), nous fournissons un (int,)
, identique à l'héritage de int
. dict
sont les attributs de classe, où nous attachons le __call__
lambda.self.__class__(self + v)
est identique à return CustomInt(self + v)
Si vous souhaitez définir une fonction à appeler plusieurs fois, vous devez tout d'abord renvoyer un objet appelable (par exemple, une fonction), sinon vous devez créer votre propre objet en définissant un attribut __call__
, afin qu'il soit appelable. .
Le point suivant est que vous devez conserver tous les arguments, ce qui signifie dans ce cas que vous voudrez peut-être utiliser Coroutines ou une fonction récursive. Mais notez que Coroutines sont beaucoup plus optimisés/flexibles que les fonctions récursives, spécialement pour de telles tâches.
Voici un exemple de fonction utilisant Coroutines, qui conserve son dernier état. Notez qu'il ne peut pas être appelé plusieurs fois car la valeur de retour est une integer
qui n'est pas appelable, mais vous pouvez envisager de le transformer en objet attendu ;-).
def add():
current = yield
while True:
value = yield current
current = value + current
it = add()
next(it)
print(it.send(10))
print(it.send(2))
print(it.send(4))
10
12
16
La méthode pythonique consiste à utiliser des arguments dynamiques:
def add(*args):
return sum(args)
Ce n'est pas la réponse que vous cherchez, et vous le savez peut-être, mais je pensais pouvoir la donner quand même, car si quelqu'un se demandait si cela ne devait pas être fait par curiosité, mais par travail. Ils devraient probablement avoir la réponse "bonne à faire".
Si vous souhaitez accepter un _()
_ supplémentaire afin de récupérer le résultat, vous pouvez utiliser functools.partial
:
_from functools import partial
def add(*args, result=0):
return partial(add, result=sum(args)+result) if args else result
_
Par exemple:
_>>> add(1)
functools.partial(<function add at 0x7ffbcf3ff430>, result=1)
>>> add(1)(2)
functools.partial(<function add at 0x7ffbcf3ff430>, result=3)
>>> add(1)(2)()
3
_
Cela permet également de spécifier plusieurs numéros à la fois:
_>>> add(1, 2, 3)(4, 5)(6)()
21
_
Si vous souhaitez le limiter à un seul numéro, vous pouvez procéder comme suit:
_def add(x=None, *, result=0):
return partial(add, result=x+result) if x is not None else result
_
Si vous voulez que add(x)(y)(z)
renvoie facilement le résultat et que soit appelable, le sous-classement int
est le chemin à parcourir.
Simplement:
class add(int):
def __call__(self, n):
return add(self + n)