En Codewars.com encontré la siguiente tarea:
Cree una función
addque sume números cuando se llame en sucesión. Entoncesadd(1)debería volver1,add(1)(2)debería volver1+2, ...
Si bien estoy familiarizado con los conceptos básicos de Python, nunca he encontrado una función que se pueda llamar en tal sucesión, es decir, una función f(x)que se pueda llamar como f(x)(y)(z).... Hasta ahora, ni siquiera estoy seguro de cómo interpretar esta notación.
Como matemático, sospecho que f(x)(y)es una función que asigna a cada xuna función g_{x}y luego devuelve g_{x}(y)y lo mismo para f(x)(y)(z).
Si esta interpretación fuera correcta, Python me permitiría crear dinámicamente funciones que me parecen muy interesantes. Busqué en la web durante la última hora, pero no pude encontrar una pista en la dirección correcta. Sin embargo, como no sé cómo se llama este concepto de programación, esto puede no ser demasiado sorprendente.
¿Cómo se llama a este concepto y dónde puedo leer más sobre él?
fuente

functools.partial()| WP: CierresRespuestas:
No sé si esto es tanto un encadenamiento de funciones como un encadenamiento invocable , pero, dado que las funciones son invocables, supongo que no hay ningún daño. De cualquier manera, hay dos formas en las que puedo pensar en hacer esto:
Subclasificación
inty definición__call__:La primera forma sería con una
intsubclase personalizada que define__call__cuál devuelve una nueva instancia de sí mismo con el valor actualizado:class CustomInt(int): def __call__(self, v): return CustomInt(self + v)La función
addahora se puede definir para devolver unaCustomIntinstancia, que, como un invocable que devuelve un valor actualizado de sí mismo, se puede llamar en sucesión:>>> def add(v): ... return CustomInt(v) >>> add(1) 1 >>> add(1)(2) 3 >>> add(1)(2)(3)(44) # and so on.. 50Además, como una
intsubclase, el valor devuelto conserva el comportamiento__repr__y__str__deints. Sin embargo, para operaciones más complejas, debe definir otros dunders de manera apropiada .Como señaló @Caridorc en un comentario,
addtambién podría escribirse simplemente como:Cambiar el nombre de la clase a en
addlugar deCustomInttambién funciona de manera similar.Definir un cierre, requiere una llamada adicional para generar valor:
La única otra forma en la que puedo pensar involucra una función anidada que requiere una llamada de argumento vacía adicional para devolver el resultado. Estoy sin usar
nonlocaly optar por fijar los atributos de los objetos de función para que sea portable entre pitones: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_adderEsto se devuelve continuamente a sí mismo (
_inner_adder) que, sivalse proporciona a, lo incrementa (_inner_adder += val) y si no, devuelve el valor tal como está. Como mencioné, requiere una()llamada adicional para devolver el valor incrementado:>>> add(1)(2)() 3 >>> add(1)(2)(3)() # and so on.. 6fuente
add = CostumIntdebería funcionar también y ser más sencillo.(2*add(1)(2))(3)falla con unTypeErrorporqueintno se puede llamar. Básicamente,CustomIntse convierte a simpleintcuando se usa en cualquier contexto, excepto al llamar. Para una solución más robusta, básicamente tiene que volver a implementar todos los__*__métodos, incluidas las__r*__versiones ...CustomInten absoluto sinoaddal definirlo.Puedes odiarme, pero aquí hay una frase :)
add = lambda v: type("", (int,), {"__call__": lambda self, v: self.__class__(self + v)})(v)Editar: Ok, ¿cómo funciona esto? El código es idéntico a la respuesta de @Jim, pero todo sucede en una sola línea.
typepuede ser utilizado para construir nuevos tipos:type(name, bases, dict) -> a new type. Porquenameproporcionamos una cadena vacía, ya que el nombre no es realmente necesario en este caso. Parabases(tupla) proporcionamos un(int,), que es idéntico a heredarint.dictson los atributos de la clase, donde adjuntamos el__call__lambda.self.__class__(self + v)es idéntico areturn CustomInt(self + v)fuente
class add(int):__call__ = lambda self, v: add(self+v)Si desea definir una función para que se llame varias veces, primero debe devolver un objeto invocable cada vez (por ejemplo, una función); de lo contrario, debe crear su propio objeto definiendo un
__call__atributo para que sea invocable.El siguiente punto es que debe conservar todos los argumentos, lo que en este caso significa que es posible que desee utilizar Coroutines o una función recursiva. Pero tenga en cuenta que las corrutinas son mucho más optimizadas / flexibles que las funciones recursivas , especialmente para tales tareas.
Aquí hay una función de muestra que usa Coroutines, que conserva el último estado de sí misma. Tenga en cuenta que no se puede llamar varias veces ya que el valor de retorno es un
integer, pero podría pensar en convertir esto en su objeto esperado ;-).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 16fuente
La forma pitónica de hacer esto sería usar argumentos dinámicos:
def add(*args): return sum(args)Esta no es la respuesta que estás buscando, y es posible que lo sepas, pero pensé que te la daría de todos modos porque si alguien se preguntaba por hacer esto no por curiosidad sino por trabajo. Probablemente deberían tener la respuesta de "lo correcto".
fuente
add = sumsiSimplemente:
class add(int): def __call__(self, n): return add(self + n)fuente
Si está dispuesto a aceptar un adicional
()para recuperar el resultado, puede usarfunctools.partial:from functools import partial def add(*args, result=0): return partial(add, result=sum(args)+result) if args else resultPor ejemplo:
>>> add(1) functools.partial(<function add at 0x7ffbcf3ff430>, result=1) >>> add(1)(2) functools.partial(<function add at 0x7ffbcf3ff430>, result=3) >>> add(1)(2)() 3Esto también permite especificar varios números a la vez:
>>> add(1, 2, 3)(4, 5)(6)() 21Si desea restringirlo a un solo número, puede hacer lo siguiente:
def add(x=None, *, result=0): return partial(add, result=x+result) if x is not None else resultSi desea
add(x)(y)(z)devolver fácilmente el resultado y poder llamar más, entonces la subclasificaciónintes el camino a seguir.fuente