103 votes

Enchaînement de fonctions en Python

Sur Codewars.com J'ai rencontré la tâche suivante :

Créer une fonction add qui additionne les numéros lorsqu'ils sont appelés successivement. Ainsi, add(1) devrait retourner 1 , add(1)(2) devrait retourner 1+2 , ...

Bien que je sois familier avec les bases de Python, je n'ai jamais rencontré une fonction capable d'être appelée dans une telle succession, c'est-à-dire une fonction f(x) qui peut être appelé comme f(x)(y)(z)... . Jusqu'à présent, je ne sais même pas comment interpréter cette notation.

En tant que mathématicien, je soupçonnerais que f(x)(y) est une fonction qui attribue à chaque x une fonction g_{x} et renvoie ensuite g_{x}(y) et de même pour f(x)(y)(z) .

Si cette interprétation est correcte, Python me permettrait de créer dynamiquement des fonctions, ce qui me semble très intéressant. J'ai cherché sur le web pendant une heure, mais je n'ai pas réussi à trouver une piste dans la bonne direction. Cependant, comme je ne sais pas comment s'appelle ce concept de programmation, cela n'est peut-être pas trop surprenant.

Comment appelez-vous ce concept et où puis-je en apprendre davantage à son sujet ?

8 votes

On dirait que vous cherchez des fonctions de curry.

2 votes

Conseil : Une fonction imbriquée est créée dynamiquement, a accès aux paramètres locaux de sa fonction parentale et peut être retournée sous forme d'objet (appelable).

0 votes

@JonathonReinhart C'est comme ça que je voyais le problème. Mais je ne voyais pas vraiment comment le mettre en œuvre.

116voto

Jim Points 8793

Je ne sais pas si c'est fonction enchaînement autant qu'il est Appelable le chaînage, mais, comme les fonctions sont des calamités, je suppose qu'il n'y a pas de mal. Dans tous les cas, il y a deux façons de faire :

Sous-classement int et de définir __call__ :

La première façon serait d'utiliser un int qui définit __call__ qui renvoie une nouvelle instance de lui-même avec la valeur mise à jour :

class CustomInt(int):
    def __call__(self, v):
        return CustomInt(self + v)

Fonction add peut maintenant être défini pour retourner un CustomInt qui, en tant qu'appelable renvoyant une valeur actualisée 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

En outre, en tant que int la valeur retournée conserve l'attribut __repr__ y __str__ comportement de int s. Cependant, pour des opérations plus complexes, vous devez définir d'autres dunders de manière appropriée. .

Comme l'a noté @Caridorc dans un commentaire, add pourrait aussi être simplement écrit comme :

add = CustomInt 

Renommer la classe en add au lieu de CustomInt fonctionne également de manière similaire.


Définir une fermeture, nécessite un appel supplémentaire pour obtenir une valeur :

La seule autre façon à laquelle je peux penser implique une fonction imbriquée qui nécessite un appel d'argument vide supplémentaire afin de retourner le résultat. Je suis pas en utilisant nonlocal et opter pour l'attachement d'attributs aux objets de la fonction pour la rendre portable entre Python :

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 

Celle-ci se retourne continuellement ( _inner_adder ) qui, si un val est fourni, l'incrémente ( _inner_adder += val ) et si non, retourne la valeur telle quelle. Comme je l'ai mentionné, cela nécessite un () afin de retourner la valeur incrémentée :

>>> add(1)(2)()
3
>>> add(1)(2)(3)()  # and so on..
6

6 votes

Dans le code interactif add = CostumInt devrait également fonctionner et être plus simple.

4 votes

Le problème avec la sous-classe des built-ins est que (2*add(1)(2))(3) échoue avec un TypeError parce que int n'est pas appelable. En principe, le CustomInt est converti en simple int lorsqu'il est utilisé dans tout sauf lors d'un appel. Pour une solution plus robuste, vous devez réimplémenter toutes les fonctions de l'application __*__ méthodes, y compris le __r*__ des versions...

0 votes

@Caridorc Ou ne pas l'appeler CustomInt du tout, mais add lors de sa définition.

31voto

Jordan Jambazov Points 2175

Vous pouvez me détester, mais voici une phrase d'une seule traite :)

add = lambda v: type("", (int,), {"__call__": lambda self, v: self.__class__(self + v)})(v)

Edit : Ok, comment cela fonctionne-t-il ? Le code est identique à la réponse de @Jim, mais tout se passe sur une seule ligne.

  1. type peuvent être utilisés 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,) ce qui revient à hériter de int . dict sont les attributs de la classe, où nous attachons le __call__ lambda.
  2. self.__class__(self + v) est identique à return CustomInt(self + v)
  3. Le nouveau type est construit et retourné dans la lambda externe.

20 votes

Ou même plus court : class add(int):__call__ = lambda self, v: add(self+v)

3 votes

Le code à l'intérieur d'une classe est exécuté exactement comme du code normal. Vous pouvez donc définir des méthodes spéciales par des affectations. La seule différence est que la portée de la classe est un peu... particulière.

16voto

Kasramvd Points 32864

Si vous voulez définir une fonction qui sera appelée plusieurs fois, vous devez d'abord renvoyer un objet appelable à chaque fois (par exemple une fonction), sinon vous devez créer votre propre objet en définissant un fichier de type __call__ pour qu'il puisse être appelé.

Le point suivant est que vous devez préserver tous les arguments, ce qui dans ce cas signifie que vous pourriez vouloir utiliser Coroutines ou une fonction récursive. Mais notez que Les coroutines sont beaucoup plus optimisées/flexibles que les fonctions récursives. spécialement pour de telles tâches.

Voici un exemple de fonction utilisant les Coroutines, qui préserve le dernier état d'elle-même. Notez qu'elle ne peut pas être appelée plusieurs fois puisque la valeur de retour est un fichier integer qui n'est pas appelable, mais vous pourriez penser à en faire votre 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

13voto

Nicolae Points 1

Tout simplement :

class add(int):
   def __call__(self, n):
      return add(self + n)

1 votes

C'est facilement la meilleure réponse !

6voto

nichochar Points 189

La manière pythonique de faire cela serait d'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 j'ai pensé que je la donnerais quand même parce que si quelqu'un se demandait s'il allait le faire non pas par curiosité mais pour le travail. Ils devraient probablement avoir la réponse "la bonne chose à faire".

1 votes

J'ai enlevé votre P.S. ' note, nichochar. Nous sommes tous conscients de l'élégance de Python :-) Je ne pense pas que cela ait sa place dans le corps de la réponse.

7 votes

Je pense que tu aurais pu simplement faire add = sum si on doit prendre cette voie

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X