it-swarm.asia

Python中的函数链

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

创建一个函数add,在连续调用时将数字相加。所以add(1)应该返回1add(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将允许我动态创建对我来说非常有趣的函数。我在网上搜索过去一小时,但未能找到正确方向的领先优势。但是,由于我不知道如何调用这个编程概念,这可能不会太令人惊讶。

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

81
Stefan Mesken

我不知道这是function链是否与callable chaining一样多,但是,因为函数 callables我想这没有什么坏处。无论哪种方式,有两种方法我可以想到这样做:

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也类似。


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

我能想到的另一种方法涉及一个嵌套函数,它需要一个额外的空参数调用才能返回结果。我是使用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 

这会自动返回(_inner_adder),如果提供val,则递增它(_inner_adder += val),如果不是,则返回该值。就像我提到的那样,它需要额外的()调用才能返回递增的值:

>>> add(1)(2)()
3
>>> add(1)(2)(3)()  # and so on..
6
90
Jim Fasarakis Hilliard

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

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

编辑:好的,这是如何工作的?代码与@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内返回。
23
Jordan Jambazov

如果要定义要多次调用的函数,首先需要每次都返回一个可调用对象(例如函数),否则你必须通过定义__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
16
Kasrâmvd

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

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

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

5
nichochar

只是:

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