Em Codewars.com , encontrei a seguinte tarefa:
Crie uma função
add
que some números quando chamada em sucessão. Portanto,add(1)
deve retornar1
,add(1)(2)
deve retornar1+2
, ...
Embora esteja familiarizado com o básico do Python, nunca encontrei uma função que pudesse ser chamada em tal sucessão, ou seja, uma função f(x)
que pudesse ser chamada como f(x)(y)(z)...
. Até agora, nem tenho certeza de como interpretar essa notação.
Como matemático, suspeito que f(x)(y)
seja uma função que atribui a cada x
função a g_{x}
e depois retorna g_{x}(y)
e da mesma forma para f(x)(y)(z)
.
Se essa interpretação estiver correta, Python me permitiria criar funções dinamicamente que me parecem muito interessantes. Pesquisei na web na última hora, mas não consegui encontrar uma pista na direção certa. Como não sei como esse conceito de programação é chamado, no entanto, isso pode não ser muito surpreendente.
Como você chama esse conceito e onde posso ler mais sobre ele?
fonte
functools.partial()
| WP: EncerramentosRespostas:
Não sei se isso é encadeamento de funções tanto quanto é encadeamento chamável , mas, como as funções são chamáveis, acho que não houve nenhum problema. De qualquer forma, posso pensar em fazer isso de duas maneiras:
Subclassificando
int
e definindo__call__
:A primeira forma seria com uma
int
subclasse personalizada que define o__call__
que retorna uma nova instância de si mesma com o valor atualizado:class CustomInt(int): def __call__(self, v): return CustomInt(self + v)
A função
add
agora pode ser definida para retornar umaCustomInt
instância, que, como um chamável que retorna um valor atualizado de si mesma, pode ser chamada em sucessão:>>> def add(v): ... return CustomInt(v) >>> add(1) 1 >>> add(1)(2) 3 >>> add(1)(2)(3)(44) # and so on.. 50
Além disso, como uma
int
subclasse, o valor devolvido retém a__repr__
e__str__
comportamento deint
s. Para operações mais complexas, porém, você deve definir outros dunders apropriadamente .Como @Caridorc observou em um comentário,
add
também poderia ser escrito simplesmente como:Renomear a classe para em
add
vez deCustomInt
também funciona de maneira semelhante.Defina um fechamento, requer uma chamada extra para gerar o valor:
A única outra maneira que consigo pensar envolve uma função aninhada que requer uma chamada de argumento vazio extra para retornar o resultado. Estou não usando
nonlocal
e opt para anexar atributos para os objetos de função para torná-lo portátil 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
Este retorna continuamente a si mesmo (
_inner_adder
) que, se aval
for fornecido, o incrementa (_inner_adder += val
) e, se não, retorna o valor como está. Como mencionei, ele requer uma()
chamada extra para retornar o valor incrementado:>>> add(1)(2)() 3 >>> add(1)(2)(3)() # and so on.. 6
fonte
add = CostumInt
deve funcionar também e ser mais simples.(2*add(1)(2))(3)
falha com umTypeError
porqueint
não pode ser chamado. Basicamente, oCustomInt
é convertido em simplesint
quando usado em qualquer contexto, exceto ao chamar. Para uma solução mais robusta, você basicamente tem que__*__
__r*__
CustomInt
nenhum, masadd
ao defini-lo.Você pode me odiar, mas aqui está uma linha :)
add = lambda v: type("", (int,), {"__call__": lambda self, v: self.__class__(self + v)})(v)
Edit: Ok, como isso funciona? O código é idêntico ao da resposta do @Jim, mas tudo acontece em uma única linha.
type
pode ser usado para construir novos tipos:type(name, bases, dict) -> a new type
. Poisname
fornecemos uma string vazia, pois o nome não é realmente necessário neste caso. Parabases
(tupla), fornecemos um(int,)
, que é idêntico a herdarint
.dict
são os atributos da classe, onde anexamos o__call__
lambda.self.__class__(self + v)
é idêntico areturn CustomInt(self + v)
fonte
class add(int):__call__ = lambda self, v: add(self+v)
Se você deseja definir uma função a ser chamada várias vezes, primeiro você precisa retornar um objeto que pode ser chamado a cada vez (por exemplo, uma função), caso contrário, você deve criar seu próprio objeto definindo um
__call__
atributo, para que ele seja chamado.O próximo ponto é que você precisa preservar todos os argumentos, o que, neste caso, significa que você pode querer usar corrotinas ou uma função recursiva. Mas observe que as corrotinas são muito mais otimizadas / flexíveis do que as funções recursivas , especialmente para tais tarefas.
Aqui está um exemplo de função usando corrotinas, que preserva o estado mais recente de si mesmo. Observe que ele não pode ser chamado várias vezes, pois o valor de retorno é um
integer
que não pode ser chamado , mas você pode pensar em transformá-lo em seu 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
fonte
A maneira pythônica de fazer isso seria usar argumentos dinâmicos:
def add(*args): return sum(args)
Esta não é a resposta que você está procurando, e você deve saber disso, mas pensei em respondê-la de qualquer maneira, porque se alguém estivesse pensando em fazer isso não por curiosidade, mas por trabalho. Eles provavelmente devem ter a resposta "a coisa certa a fazer".
fonte
add = sum
se fosse por esse caminhoSimplesmente:
class add(int): def __call__(self, n): return add(self + n)
fonte
Se estiver disposto a aceitar um adicional
()
para recuperar o resultado, você pode usarfunctools.partial
:from functools import partial def add(*args, result=0): return partial(add, result=sum(args)+result) if args else result
Por exemplo:
>>> 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
Isso também permite especificar vários números de uma vez:
>>> add(1, 2, 3)(4, 5)(6)() 21
Se quiser restringir a um único número, você pode fazer o seguinte:
def add(x=None, *, result=0): return partial(add, result=x+result) if x is not None else result
Se você deseja
add(x)(y)(z)
retornar prontamente o resultado e ser posteriormente chamado, a subclasseint
é o caminho a percorrer.fonte