Python中的函数链

Posted

技术标签:

【中文标题】Python中的函数链【英文标题】:Function chaining in Python 【发布时间】:2016-12-26 13:47:14 【问题描述】:

在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 将允许我动态创建对我来说非常有趣的函数。在过去的一个小时里,我在网上搜索了一个小时,但无法找到正确方向的线索。不过,由于我不知道这个编程概念是如何被调用的,所以这可能不会太令人惊讶。

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

【问题讨论】:

看起来你正在寻找柯里化函数 提示:嵌套函数是动态创建的,可以访问其父函数的局部变量,并且能够作为(可调用的)对象返回。 @JonathonReinhart 这就是我思考问题的方式。但我并没有真正看到如何实现它。 顺便说一句:Python肯定允许你动态创建函数。如果您有兴趣,可以阅读以下几个相关概念:WP: First-class functions | How do you make a higher order function in Python? | functools.partial() | WP: Closures @LukasGraf 我会看看它。谢谢! 【参考方案1】:

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

from functools import partial

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

例如:

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

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

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

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

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

如果您希望 add(x)(y)(z) 轻松返回结果并且可以进一步调用,那么子类化 int 是要走的路。

【讨论】:

【参考方案2】:

简单地说:

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

【讨论】:

这无疑是最好的答案!【参考方案3】:

我不知道这是否是 function 链接和 callable 链接一样多,但是,由于函数 are 是可调用的,我猜有没有造成伤害。无论哪种方式,我都可以想到两种方法:

子类int并定义__call__

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

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

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

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

另外,作为int的子类,返回值保留ints的__repr____str__行为。 不过,对于更复杂的操作,您应该适当地定义其他 dunders

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

add = CustomInt 

将类重命名为 add 而不是 CustomInt 也同样有效。


定义一个闭包,需要额外调用 yield 值:

我能想到的唯一其他方法涉及一个嵌套函数,该函数需要一个额外的空参数调用才能返回结果。我使用 nonlocal 并选择将属性附加到函数对象以使其在 Python 之间可移植:

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 

这会不断返回自身 (_inner_adder),如果提供了 val,则将其递增 (_inner_adder += val),如果没有,则按原样返回值。就像我提到的,它需要一个额外的() 调用才能返回增加的值:

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

【讨论】:

在交互代码中add = CostumInt 应该也可以工作并且更简单。 子类化内置函数的问题是(2*add(1)(2))(3) 失败并返回TypeError,因为int 不可调用。基本上,CustomIntany 上下文中使用时会转换为普通的int,调用时除外。要获得更强大的解决方案,您基本上必须重新实现所有 __*__ 方法,包括 __r*__ 版本... @Caridorc 或者在定义它时根本不叫它CustomInt 而叫它add【参考方案4】:

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

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

这不是您要寻找的答案,您可能知道这一点,但我想我还是会给出它,因为如果有人想知道这样做不是出于好奇而是为了工作。他们可能应该有“正确的事情”的答案。

【讨论】:

我删除了你的“P.S”字条,nichochar。我们都知道 Python 有多优雅 :-) 我认为它不属于答案的主体。 我确实认为如果要走那条路,你本来可以完成add = sum【参考方案5】:

你可以恨我,但这里是一条线:)

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

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

    type 可用于构造新类型:type(name, bases, dict) -&gt; a new type。对于name,我们提供空字符串,因为在这种情况下实际上不需要名称。对于bases(元组),我们提供(int,),这与继承int 相同。 dict 是类属性,我们在其中附加 __call__ lambda。 self.__class__(self + v) 等同于 return CustomInt(self + v) 新类型在外部 lambda 中构造并返回。

【讨论】:

甚至更短:class add(int):__call__ = lambda self, v: add(self+v) 类中的代码与普通代码完全相同,因此您可以通过赋值定义特殊方法。唯一的区别是类范围有点……奇特。【参考方案6】:

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

下一点是您需要保留所有参数,在这种情况下,这意味着您可能想要使用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

【讨论】:

以上是关于Python中的函数链的主要内容,如果未能解决你的问题,请参考以下文章

1.3.1 区块链中的加密算法——Hash算法(更新)

科技康康区块链中的哈希算法

哈希函数在区块链中的应用

承诺链中的返回值没有被调用

复杂承诺返回链中的承诺 catch() 顺序

区块链与密码学第5-6讲:哈希哈数在区块链中的应用