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
Function chaining in Python
提问by Stefan Mesken
On codewars.com I encountered the following task:
在 codewars.com 上,我遇到了以下任务:
Create a function
add
that adds numbers together when called in succession. Soadd(1)
should return1
,add(1)(2)
should return1+2
, ...
创建一个
add
在连续调用时将数字相加的函数。所以add(1)
应该返回1
,add(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 x
a 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 int
and defining __call__
:
子分类int
和定义__call__
:
The first way would be with a custom int
subclass 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 add
can now be defined to return a CustomInt
instance, 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 int
subclass, the returned value retains the __repr__
and __str__
behavior of int
s. For more complex operations though, you should define other dunders appropriately.
另外,作为一个int
子类,返回值保留了s的__repr__
和__str__
行为int
。但是,对于更复杂的操作,您应该适当地定义其他错误。
As @Caridorc noted in a comment, add
could also be simply written as:
正如@Caridorc 在评论中指出的,add
也可以简单地写成:
add = CustomInt
Renaming the class to add
instead of CustomInt
also 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 nonlocal
and 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 val
is 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 的答案相同,但一切都发生在一行中。
type
can be used to construct new types:type(name, bases, dict) -> a new type
. Forname
we provide empty string, as name is not really needed in this case. Forbases
(tuple) we provide an(int,)
, which is identical to inheritingint
.dict
are the class attributes, where we attach the__call__
lambda.self.__class__(self + v)
is identical toreturn CustomInt(self + v)
- The new type is constructed and returned within the outer lambda.
type
可用于构造新类型:type(name, bases, dict) -> a new type
. 因为name
我们提供空字符串,因为在这种情况下实际上不需要名称。对于bases
(元组),我们提供了一个(int,)
,它与继承int
.dict
是类属性,我们在其中附加了__call__
lambda。self.__class__(self + v)
等同于return CustomInt(self + v)
- 新类型在外部 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 integer
which 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 int
is the way to go.
如果您想add(x)(y)(z)
轻松地返回结果并进一步调用,那么子类化int
是一种可行的方法。
回答by Nicolae
Simply:
简单地:
class add(int):
def __call__(self, n):
return add(self + n)