Python中的函数链接


90

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将允许我动态创建对我来说非常有趣的函数。我在过去一个小时内一直在网上搜索,但找不到正确方向的线索。但是,由于我不知道如何调用此编程概念,所以这可能并不奇怪。

您如何称呼这个概念?在哪里可以阅读更多有关它的信息?


7
看起来您正在寻找
流动性

2
提示:嵌套函数是动态创建的,可以访问其父函数的局部变量,并且可以作为(可调用的)对象返回。
乔纳森·莱因哈特

@JonathonReinhart这就是我思考问题的方式。但是我并没有真正看到如何实现它。
Stefan Mesken

3
顺便说一句:Python肯定会允许您动态创建函数。如果您有兴趣,请阅读以下两个相关概念:WP:一流的功能| 您如何在Python中制作高阶函数?| functools.partial()| WP:停工
Lukas Graf

@LukasGraf我来看一下。谢谢!
Stefan Mesken

Answers:


100

我不知道函数链接和可调用链接是否一样多,但是,由于函数可调用的,所以我猜没有什么坏处。无论哪种方式,我都可以通过两种方式来考虑:

子类化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子类,返回值保留的__repr____str__行为int但是,对于更复杂的操作,应适当定义其他标记

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

add = CustomInt 

将类重命名为add代替CustomInt也可以类似地工作。


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

我能想到的唯一其他方法涉及一个嵌套函数,该函数需要一个额外的空参数调用才能返回结果。我没有使用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提供了a,则将其递增(_inner_adder += val),否则,将按原样返回值。就像我提到的,它需要额外的()调用才能返回增加的值:

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

6
在交互式代码中也add = CostumInt应该起作用并且更简单。
Caridorc '16

4
内建子类化的问题是,由于无法调用而导致(2*add(1)(2))(3)失败。基本上,在任何上下文(除非调用)中使用时,都会转换为Plain 。为了获得更强大的解决方案,您基本上必须重新实现所有方法,包括版本...TypeErrorintCustomIntint__*____r*__
Bakuriu

@Caridorc还是不要把它CustomInt所有,但add定义它的时候。
minipif

27

你可以恨我,但这是单线的:)

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,),与继承相同intdict是类属性,我们在其中附加了__call__lambda。
  2. self.__class__(self + v) 等同于 return CustomInt(self + v)
  3. 新类型将在外部lambda中构造并返回。

17
甚至更短:class add(int):__call__ = lambda self, v: add(self+v)
Bakuriu

3
类中的代码与普通代码完全一样地执行,因此您可以通过分配来定义特殊方法。唯一的区别是类范围有点...特殊。
巴库里

16

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

接下来的一点是,您需要保留所有参数,在这种情况下,这意味着您可能要使用协程或递归函数。但是请注意,协程比递归函数更优化/更灵活,特别是对于此类任务。

这是使用协程的示例函数,该函数保留其自身的最新状态。请注意,由于返回值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

6

执行此操作的pythonic方法是使用动态参数:

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

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


1
我删除了您的PS笔记nichochar。我们都知道Python是多么的优雅:-)我不认为Python属于答案的主体。
Dimitris Fasarakis Hilliard'8

6
我确实认为,add = sum如果要走那条路,您可能就已经做好了
codykochmann '17

3

只是:

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

3

如果您愿意接受其他内容()以检索结果,则可以使用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是您的理想选择。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.