Python中的函数链
Posted
技术标签:
【中文标题】Python中的函数链【英文标题】:Function chaining in Python 【发布时间】:2016-12-26 13:47:14 【问题描述】:在Codewars.com我遇到了以下任务:
创建一个函数
add
,在连续调用时将数字相加。所以add(1)
应该返回1
,add(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
的子类,返回值保留int
s的__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
不可调用。基本上,CustomInt
在 any 上下文中使用时会转换为普通的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) -> 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中的函数链的主要内容,如果未能解决你的问题,请参考以下文章