Python 中的函数链

Codewars.com上,我遇到了以下任务:

创建一个函数 add,在连续调用时将数字相加。所以 add(1)应该返回 1add(1)(2)应该返回 1+2,..。

虽然我熟悉 Python 的基础知识,但我从未遇到过能够以这种顺序调用的函数,即可以调用为 f(x)(y)(z)...的函数 f(x)。到目前为止,我甚至不知道如何解释这个符号。

作为一个数学家,我怀疑 f(x)(y)是一个函数,它为每个 x分配一个函数 g_{x},然后返回 g_{x}(y),对于 f(x)(y)(z)也是如此。

如果这种解释是正确的,Python 将允许我动态地创建对我来说非常有趣的函数。我在网上搜索了一个小时,但没有找到正确方向的线索。不过,由于我不知道这个编程概念是如何调用的,因此这可能并不太令人惊讶。

你怎么称呼这个概念,我可以在哪里阅读更多有关它的内容?

60456 次浏览

I don't know whether this is function chaining as much as it's callable chaining, but, since functions are callables I guess there's no harm done. Either way, there's two ways I can think of doing this:

Sub-classing int and defining __call__:

The first way would be with a custom int subclass that defines __call__ which returns a new instance of itself with the updated value:

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

Function add can now be defined to return a CustomInt instance, which, as a callable that returns an updated value of itself, can be called in succession:

>>> def add(v):
...    return CustomInt(v)
>>> add(1)
1
>>> add(1)(2)
3
>>> add(1)(2)(3)(44)  # and so on..
50

In addition, as an int subclass, the returned value retains the __repr__ and __str__ behavior of ints. For more complex operations though, you should define other dunders appropriately.

As @Caridorc noted in a comment, add could also be simply written as:

add = CustomInt

Renaming the class to add instead of CustomInt also works similarly.


Define a closure, requires extra call to yield value:

The only other way I can think of involves a nested function that requires an extra empty argument call in order to return the result. I'm not using nonlocal and opt for attaching attributes to the function objects to make it portable between 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

This continuously returns itself (_inner_adder) which, if a val is supplied, increments it (_inner_adder += val) and if not, returns the value as it is. Like I mentioned, it requires an extra () call in order to return the incremented value:

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

If you want to define a function to be called multiple times, first you need to return a callable object each time (for example a function) otherwise you have to create your own object by defining a __call__ attribute, in order for it to be callable.

The next point is that you need to preserve all the arguments, which in this case means you might want to use Coroutines or a recursive function. But note that Coroutines are much more optimized/flexible than recursive functions, specially for such tasks.

Here is a sample function using Coroutines, that preserves the latest state of itself. Note that it can't be called multiple times since the return value is an integer which is not callable, but you might think about turning this into your expected object ;-).

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

You can hate me, but here is a one-liner :)

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

Edit: Ok, how this works? The code is identical to answer of @Jim, but everything happens on a single line.

  1. type can be used to construct new types: type(name, bases, dict) -> a new type. For name we provide empty string, as name is not really needed in this case. For bases (tuple) we provide an (int,), which is identical to inheriting int. dict are the class attributes, where we attach the __call__ lambda.
  2. self.__class__(self + v) is identical to return CustomInt(self + v)
  3. The new type is constructed and returned within the outer lambda.

The pythonic way to do this would be to use dynamic arguments:

def add(*args):
return sum(args)

This is not the answer you're looking for, and you may know this, but I thought I would give it anyway because if someone was wondering about doing this not out of curiosity but for work. They should probably have the "right thing to do" answer.

Simply:

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

If you are willing to accept an additional () in order to retrieve the result you can use functools.partial:

from functools import partial


def add(*args, result=0):
return partial(add, result=sum(args)+result) if args else result

For example:

>>> 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

This also allows specifying multiple numbers at once:

>>> add(1, 2, 3)(4, 5)(6)()
21

If you want to restrict it to a single number you can do the following:

def add(x=None, *, result=0):
return partial(add, result=x+result) if x is not None else result

If you want add(x)(y)(z) to readily return the result and be further callable then sub-classing int is the way to go.