En Codewars.com encontré la siguiente tarea:
Cree una función
add
que 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 x
una 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
int
y definición__call__
:La primera forma sería con una
int
subclase 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
add
ahora se puede definir para devolver unaCustomInt
instancia, 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.. 50
Además, como una
int
subclase, el valor devuelto conserva el comportamiento__repr__
y__str__
deint
s. Sin embargo, para operaciones más complejas, debe definir otros dunders de manera apropiada .Como señaló @Caridorc en un comentario,
add
también podría escribirse simplemente como:Cambiar el nombre de la clase a en
add
lugar deCustomInt
tambié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
nonlocal
y 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_adder
Esto se devuelve continuamente a sí mismo (
_inner_adder
) que, sival
se 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.. 6
fuente
add = CostumInt
debería funcionar también y ser más sencillo.(2*add(1)(2))(3)
falla con unTypeError
porqueint
no se puede llamar. Básicamente,CustomInt
se convierte a simpleint
cuando 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 ...CustomInt
en absoluto sinoadd
al 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.
type
puede ser utilizado para construir nuevos tipos:type(name, bases, dict) -> a new type
. Porquename
proporcionamos 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
.dict
son 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 16
fuente
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 = sum
siSimplemente:
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 result
Por 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)() 3
Esto también permite especificar varios números a la vez:
>>> add(1, 2, 3)(4, 5)(6)() 21
Si 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 result
Si desea
add(x)(y)(z)
devolver fácilmente el resultado y poder llamar más, entonces la subclasificaciónint
es el camino a seguir.fuente