Python中的函数链

Ste*_*ken 84 python function python-3.x

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

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

虽然我熟悉Python的基础知识,但我从来没有遇到过能够连续调用的函数f(x),即可以被称为的函数f(x)(y)(z)....到目前为止,我甚至不确定如何解释这种表示法.

作为一个数学家,我怀疑f(x)(y)是分配到每个功能x的函数g_{x},然后返回g_{x}(y),同样也f(x)(y)(z).

如果这种解释是正确的,Python将允许我动态创建对我来说非常有趣的函数.我在网上搜索过去一小时,但未能找到正确方向的领先优势.因为我不知道如何调用这个编程概念,所以这可能不会太令人惊讶.

你如何称呼这个概念,我在哪里可以阅读更多相关信息?

Jim*_*ard 94

我不知道这是否是函数链,就像它的可调用链一样,但是,因为函数可调用的,我想这没有什么坏处.无论哪种方式,有两种方法我可以想到这样做:

子类int和定义__call__:

第一种方法是使用自定义int子类定义__call__哪个子类返回具有更新值的新实例:

class CustomInt(int):
    def __call__(self, v):
        return CustomInt(self + v)
Run Code Online (Sandbox Code Playgroud)

add现在可以定义函数以返回一个CustomInt实例,该实例可以连续调用,作为返回其自身更新值的可调用对象:

>>> def add(v):
...    return CustomInt(v)
>>> add(1)
1
>>> add(1)(2)
3
>>> add(1)(2)(3)(44)  # and so on..
50
Run Code Online (Sandbox Code Playgroud)

另外,作为一个int子类中,返回的值保留__repr____str__行为int秒.但是,对于更复杂的操作,您应该适当地定义其他dunders.

正如@Caridorc在评论中指出的那样,add也可以简单地写成:

add = CustomInt 
Run Code Online (Sandbox Code Playgroud)

重命名类add而不是CustomInt也类似.


定义一个闭包,需要额外的调用才能产生价值:

我能想到的另一种方法涉及一个嵌套函数,它需要一个额外的空参数调用才能返回结果.我没有使用nonlocal并选择将属性附加到函数对象以使其在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 
Run Code Online (Sandbox Code Playgroud)

这会连续返回self(_inner_adder),如果val提供了a ,则递增it(_inner_adder += val),如果不是,则返回值.就像我提到的,它需要额外的()调用才能返回递增的值:

>>> add(1)(2)()
3
>>> add(1)(2)(3)()  # and so on..
6
Run Code Online (Sandbox Code Playgroud)

  • 在交互式代码中,"add = CostumInt"也应该工作并且更简单. (6认同)
  • 子类化内置函数的问题是`(2*add(1)(2))(3)`失败并带有`TypeError`,因为`int`不可调用.基本上,`CustomInt`在*any*context中使用时转换为plain`int`,除非在调用时.对于更强大的解决方案,您基本上必须重新实现所有`__*__`方法,包括`__r*__`版本...... (4认同)

Jor*_*zov 23

你可以恨我,但这是一个单行:)

add = lambda v: type("", (int,), {"__call__": lambda self, v: self.__class__(self + v)})(v)
Run Code Online (Sandbox Code Playgroud)

编辑:好的,这是如何工作的?代码与@Jim的答案相同,但一切都在一行上发生.

  1. type可用于构造新类型:type(name, bases, dict) -> a new type.因为name我们提供空字符串,因为在这种情况下不需要名称.对于bases(元组)我们提供一个(int,),与继承相同int.dict是类属性,我们附加__call__lambda.
  2. self.__class__(self + v) 是完全相同的 return CustomInt(self + v)
  3. 构造新类型并在外部lambda内返回.

  • 甚至更短:`class add(int):__ call__ = lambda self,v:add(self + v)` (14认同)
  • 类中的代码与普通代码完全相同,因此您可以通过赋值来定义特殊方法.唯一的区别是类范围有点......特殊. (3认同)

Kas*_*mvd 16

如果要定义要多次调用的函数,首先需要每次返回一个可调用对象(例如函数),否则必须通过定义__call__属性来创建自己的对象,以使其可调用.

下一点是您需要保留所有参数,在这种情况下,您可能希望使用Coroutines或递归函数.但请注意,Coroutines比递归函数更加优化/灵活,特别是对于此类任务.

这是一个使用Coroutines的示例函数,它保留了自身的最新状态.请注意,它不能被多次integer调用,因为返回值是不可调用的,但您可能会考虑将其转换为您期望的对象;-).

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
Run Code Online (Sandbox Code Playgroud)


小智 8

简单地:

class add(int):
   def __call__(self, n):
      return add(self + n)
Run Code Online (Sandbox Code Playgroud)

  • 这无疑是最好的答案! (2认同)

a_g*_*est 7

如果您愿意接受额外()的费用以检索结果,您可以使用functools.partial

from functools import partial

def add(*args, result=0):
    return partial(add, result=sum(args)+result) if args else result
Run Code Online (Sandbox Code Playgroud)

例如:

>>> 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
Run Code Online (Sandbox Code Playgroud)

这也允许一次指定多个数字:

>>> add(1, 2, 3)(4, 5)(6)()
21
Run Code Online (Sandbox Code Playgroud)

如果要将其限制为单个数字,可以执行以下操作:

def add(x=None, *, result=0):
    return partial(add, result=x+result) if x is not None else result
Run Code Online (Sandbox Code Playgroud)

如果您想add(x)(y)(z)轻松地返回结果进一步调用,那么子类化int是一种可行的方法。


nic*_*har 5

这样做的pythonic方法是使用动态参数:

def add(*args):
    return sum(args)
Run Code Online (Sandbox Code Playgroud)

这不是你正在寻找的答案,而且你可能知道这一点,但我认为无论如何我会放弃它,因为如果有人想知道这样做不是出于好奇而是出于工作.他们应该有"正确的事情"回答.

  • 我认为如果要走这条路,你可以做一下`add = sum` (4认同)