Sur Codewars.com, j'ai rencontré la tâche suivante:
Créez une fonction
add
qui ajoute des nombres ensemble lorsqu'elle est appelée successivement. Doncadd(1)
devrait revenir1
,add(1)(2)
devrait revenir1+2
, ...
Bien que je connaisse les bases de Python, je n'ai jamais rencontré une fonction qui puisse être appelée dans une telle succession, c'est-à-dire une fonction f(x)
qui peut être appelée 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çonne que f(x)(y)
c'est une fonction qui assigne à chaque x
fonction une g_{x}
puis retourneg_{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 au cours de la dernière heure, mais je n'ai pas été en mesure de trouver une piste dans la bonne direction. Puisque je ne sais pas comment ce concept de programmation est appelé, cela n'est peut-être pas trop surprenant.
Comment appelez-vous ce concept et où puis-je en savoir plus?
la source
functools.partial()
| WP: ClosuresRéponses:
Je ne sais pas si c'est autant le chaînage de fonctions que le chaînage appelable , mais comme les fonctions sont appelables, je suppose qu'il n'y a pas de mal. Quoi qu'il en soit, il y a deux façons dont je peux penser à cela:
Sous-classification
int
et définition__call__
:La première méthode serait avec une
int
sous-classe personnalisée qui définit__call__
laquelle renvoie une nouvelle instance d'elle-même 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 uneCustomInt
instance, qui, en tant qu'appelable qui renvoie 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
int
sous-classe, la valeur renvoyée conserve le comportement__repr__
et__str__
deint
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, cela
add
pourrait également être simplement écrit comme suit :Renommer la classe en au
add
lieu deCustomInt
fonctionne également de la même manière.Définir une fermeture, nécessite un appel supplémentaire pour générer de la valeur:
Le seul autre moyen auquel je puisse penser implique une fonction imbriquée qui nécessite un appel d'argument vide supplémentaire afin de renvoyer le résultat. Je n'utilise pas
nonlocal
et j'opte pour attacher des attributs aux objets de 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
Cela retourne continuellement lui-même (
_inner_adder
) qui, si aval
est fourni, l'incrémente (_inner_adder += val
) et sinon, retourne la valeur telle qu'elle est. Comme je l'ai mentionné, il nécessite un()
appel supplémentaire pour renvoyer la valeur incrémentée:>>> add(1)(2)() 3 >>> add(1)(2)(3)() # and so on.. 6
la source
add = CostumInt
devrait fonctionner aussi et être plus simple.(2*add(1)(2))(3)
échoue avec unTypeError
carint
n'est pas appelable. Fondamentalement, leCustomInt
est converti en plainint
lorsqu'il est utilisé dans n'importe quel contexte, sauf lors de l'appel. Pour une solution plus robuste, vous devez essentiellement__*__
__r*__
CustomInt
du tout maisadd
lors de sa définition.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
. Carname
nous fournissons une chaîne vide, car le nom n'est pas vraiment nécessaire dans ce cas. Pourbases
(tuple), nous fournissons un(int,)
, qui est identique à l'héritageint
.dict
sont les attributs de classe, où nous attachons le__call__
lambda.self.__class__(self + v)
est identique àreturn CustomInt(self + v)
la source
class add(int):__call__ = lambda self, v: add(self+v)
Si vous voulez définir une fonction à appeler 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
__call__
attribut, afin qu'il soit appelable.Le point suivant est que vous devez conserver tous les arguments, ce qui dans ce cas signifie que vous souhaiterez peut-être utiliser des Coroutines ou une fonction récursive. Mais notez que 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 Coroutines, qui préserve le dernier état d'elle-même. Notez qu'il ne peut pas être appelé plusieurs fois car la valeur de retour est un
integer
qui n'est pas appelable, mais vous pourriez penser à le transformer en 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
la source
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 recherchez, et vous le savez peut-être, mais je pensais que je la donnerais quand même parce que si quelqu'un se posait la question de le faire, non par curiosité mais pour le travail. Ils devraient probablement avoir la «bonne chose à faire».
la source
add = sum
si vous alliez dans cette voieSimplement:
class add(int): def __call__(self, n): return add(self + n)
la source
Si vous êtes prêt à en accepter un supplémentaire
()
afin de récupérer le résultat, vous pouvez utiliserfunctools.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 effectuer les opérations suivantes:
def add(x=None, *, result=0): return partial(add, result=x+result) if x is not None else result
Si vous souhaitez
add(x)(y)(z)
renvoyer facilement le résultat et être plus appelable, le sous-classementint
est la solution.la source