Python 中的函数链

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/39038358/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 21:47:57  来源:igfitidea点击:

Function chaining in Python

pythonfunctionpython-3.x

提问by Stefan Mesken

On codewars.com I encountered the following task:

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

Create a function addthat adds numbers together when called in succession. So add(1)should return 1, add(1)(2)should return 1+2, ...

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

While I'm familiar with the basics of Python, I've never encountered a function that is able to be called in such succession, i.e. a function f(x)that can be called as f(x)(y)(z).... Thus far, I'm not even sure how to interpret this notation.

虽然我熟悉 Python 的基础知识,但我从未遇到过可以这样连续调用的函数f(x),即可以调用为f(x)(y)(z).... 到目前为止,我什至不确定如何解释这个符号。

As a mathematician, I'd suspect that f(x)(y)is a function that assigns to every xa function g_{x}and then returns g_{x}(y)and likewise for f(x)(y)(z).

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

Should this interpretation be correct, Python would allow me to dynamically create functions which seems very interesting to me. I've searched the web for the past hour, but wasn't able to find a lead in the right direction. Since I don't know how this programming concept is called, however, this may not be too surprising.

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

How do you call this concept and where can I read more about it?

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

回答by Dimitris Fasarakis Hilliard

I don't know whether this is functionchaining as much as it's callablechaining, but, since functions arecallables I guess there's no harm done. Either way, there's two ways I can think of doing this:

我不知道这是否是函数链和可调用链一样多,但是,由于函数可调用的,我想这没有什么坏处。无论哪种方式,我可以想到两种方法:

Sub-classing intand defining __call__:

子分类int和定义__call__

The first way would be with a custom intsubclass that defines __call__which returns a new instance of itself with the updated value:

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

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

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

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

>>> 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 intsubclass, the returned value retains the __repr__and __str__behavior of ints. For more complex operations though, you should define other dunders appropriately.

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

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

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

add = CustomInt 

Renaming the class to addinstead of CustomIntalso works similarly.

将类重命名为add而不是CustomInt也类似。



Define a closure, requires extra call to yield value:

定义一个闭包,需要额外调用 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 notusing nonlocaland opt for attaching attributes to the function objects to make it portable between Pythons:

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

This continuously returns itself (_inner_adder) which, if a valis 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:

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

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

回答by Jordan Jambazov

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.

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

  1. typecan be used to construct new types: type(name, bases, dict) -> a new type. For namewe 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. dictare 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.
  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 内构造并返回。

回答by Kasramvd

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.

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

The next point is that you need to preserve all the arguments, which in this case means you might want to use Coroutinesor 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 integerwhich is not callable, but you might think about turning this into your expected object ;-).

这是一个使用协程的示例函数,它保留了自身的最新状态。请注意,它不能被多次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

回答by nichochar

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

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

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.

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

回答by a_guest

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

如果您愿意接受额外()的费用以检索结果,您可以使用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 andbe further callable then sub-classing intis the way to go.

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

回答by Nicolae

Simply:

简单地:

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