PEP 309 – 部分函数应用
- 作者:
- Peter Harris <scav at blueyonder.co.uk>
- 状态:
- 最终
- 类型:
- 标准轨迹
- 创建时间:
- 2003-02-08
- Python 版本:
- 2.5
- 历史记录:
- 2003-02-10, 2003-02-27, 2004-02-22, 2006-04-28
注意
在接受此 PEP 后,在 python-dev 和 comp.lang.python 上的进一步讨论表明,人们希望使用一些操作函数对象的工具,但这些工具与函数式编程无关。与其为这些工具创建一个新模块,不如将“functional”模块重命名为“functools”,以反映其新扩展的关注点 [1]。
出于历史原因,此 PEP 中对“functional”模块的引用保留了下来。
摘要
此提案是关于一个函数或可调用类,该函数或可调用类允许从可调用对象和部分参数列表(包括位置参数和关键字参数)构造一个新的可调用对象。
我建议创建一个名为“functional”的标准库模块,用于存放有用的高阶函数,包括 partial() 的实现。
已将实现提交至 SourceForge [2]。
接受
修补程序 #941881 于 2005 年针对 Py2.5 被接受并应用。它基本上与这里概述的一致,一个 partial() 类型构造函数,它绑定最左侧的位置参数和任何关键字。partial 对象具有三个只读属性:func、args 和 keywords。对 partial 对象的调用可以指定覆盖对象本身中的关键字的关键字。
关于是否修改 partial 实现以包含 __get__ 方法以更 closely 模仿等效函数的行为,存在着独立且持续的讨论。
动机
在函数式编程中,函数柯里化是使用单参数函数实现多参数函数的一种方法。具有 N 个参数的函数实际上是一个具有 1 个参数的函数,它返回另一个接受 (N-1) 个参数的函数。在 Haskell 和 ML 等语言中,函数应用的工作原理是,函数调用
f x y z
实际上意味着
(((f x) y) z)
这只是一个模糊的理论问题,但事实证明,在实际编程中,这非常有用。用对另一个函数的部分参数应用来表达函数既优雅又强大,并且在函数式语言中,它被广泛使用。
在一些函数式语言中(例如 Miranda),可以使用类似于 (+1)
的表达式来表示 Python 的 (lambda x: x + 1)
。
一般来说,这类语言是强类型的,所以编译器始终知道预期的参数数量,并且在呈现函子和预期参数数量少于预期参数数量的情况下,可以执行正确的操作。
Python 并没有使用柯里化来实现多参数函数,因此如果你想要一个具有部分应用参数的函数,你可能会像上面那样使用 lambda,或者为每个实例定义一个命名函数。
但是,至少可以说,lambda 语法并不是每个人的喜好。此外,Python 使用位置参数和关键字参数的灵活参数传递提供了一个机会来推广部分应用的概念,并做一些 lambda 做不到的事情。
示例实现
以下是在 Python 中创建具有部分应用参数的可调用对象的一种方法。下面的实现基于 Scott David Daniels 提供的改进
class partial(object):
def __init__(*args, **kw):
self = args[0]
self.fn, self.args, self.kw = (args[1], args[2:], kw)
def __call__(self, *args, **kw):
if kw and self.kw:
d = self.kw.copy()
d.update(kw)
else:
d = kw or self.kw
return self.fn(*(self.args + args), **d)
(Python Cookbook 中有一段时间以来一直存在类似的食谱 [3]。)
请注意,当将该对象像函数一样调用时,位置参数将附加到提供给构造函数的参数,而关键字参数将覆盖和增强提供给构造函数的参数。
在创建对象和调用对象时,可以提供位置参数、关键字参数或两者。
使用示例
因此,partial(operator.add, 1)
类似于 (lambda x: 1 + x)
。当然,这不是你看到好处的地方。
请注意,你可以以相同的方式包装一个类,因为类本身是对象的可调用工厂。因此,在某些情况下,你可以通过对构造函数的参数进行部分应用来专门化类,而不是定义子类。
例如,partial(Tkinter.Label, fg='blue')
创建了默认情况下具有蓝色前景色 Tkinter 标签。
这是一个简单的示例,它使用部分应用来动态地为 Tkinter 小部件构造回调
from Tkinter import Tk, Canvas, Button
import sys
from functional import partial
win = Tk()
c = Canvas(win,width=200,height=50)
c.pack()
for colour in sys.argv[1:]:
b = Button(win, text=colour,
command=partial(c.config, bg=colour))
b.pack(side='left')
win.mainloop()
废弃的语法提案
我最初建议使用语法 fn@(*args, **kw)
,它与 partial(fn, *args, **kw)
相同。
@ 符号在一些汇编语言中用于暗示寄存器间接寻址,这里的使用也是一种间接寻址。 f@(x)
不是 f(x)
,而是在你调用它时变为 f(x)
的东西。
它没有得到很好的接受,因此我撤回了提案的这部分。无论如何,@ 已经被用于新的装饰器语法。
来自 comp.lang.python 和 python-dev 的反馈
表达的观点包括以下内容(我进行了概括)
- Lambda 足够好。
- @ 语法很丑(一致同意)。
- 它实际上是柯里化而不是闭包。ActiveState 的 Python Cookbook 上有一个几乎完全相同的柯里化类实现。
- 柯里化类确实是一个对标准库的有用补充。
- 它不是函数柯里化,而是部分应用。因此,现在建议将名称改为 partial()。
- 它可能不够有用,不适合作为内置函数。
- 名为
functional
的模块的概念得到了很好的接受,而且还有其他一些东西应该放在那里(例如函数组合)。 - 为了完整起见,另一个在函数调用中提供的参数之后附加部分参数的对象(可能称为
rightcurry
)已经被建议。
我同意 lambda 通常足够好,只是并不总是如此。我希望能够进行有用的自省和子类化。
我不同意 @ 特别丑陋,但可能是我很奇怪。我们有字典、列表和元组文字,它们通过特殊标点符号整齐地区分——直接表达部分应用函数文字的方式并不是那么牵强。然而,没有一个人说他们喜欢它,所以就我而言,它就像一只死鹦鹉。
我同意将该类称为 partial,而不是 curry 或 closure,因此我在此 PEP 中相应地修改了提案。但并没有贯穿始终:一些错误的“curry”引用被保留了下来,因为讨论当时处于那个阶段。
从右侧部分应用参数,或在任意位置插入参数会带来自身的问题,但在发现良好的实现和非混乱语义之前,我认为不应该排除这种可能性。
Carl Banks 发表了一个作为真实函数式闭包的实现
def curry(fn, *cargs, **ckwargs):
def call_fn(*fargs, **fkwargs):
d = ckwargs.copy()
d.update(fkwargs)
return fn(*(cargs + fargs), **d)
return call_fn
他向我保证这更有效率。
我还使用 Pyrex 对该类进行了编码,以估计通过用 C 编码可以提高多少性能
cdef class curry:
cdef object fn, args, kw
def __init__(self, fn, *args, **kw):
self.fn=fn
self.args=args
self.kw = kw
def __call__(self, *args, **kw):
if self.kw: # from Python Cookbook version
d = self.kw.copy()
d.update(kw)
else:
d=kw
return self.fn(*(self.args + args), **d)
由于要完全通用,它必须通过 Python API 调用来运行,因此 Pyrex 中的性能提升不到嵌套函数实现的 100%。出于同样的原因,C 实现不太可能快很多,所以用 C 编码内置函数的理由并不充分。
总结
我更希望标准库中存在一些方法来部分应用函数和其他可调用对象。
标准库模块 functional
应该包含 partial
的实现,以及社区想要的任何其他高阶函数。其他可能属于该模块的函数超出了此 PEP 的范围。
实现、文档和单元测试的补丁(分别为 SF 补丁 931005、931007 和 931010)已提交,但尚未签入。
Hye-Shik Chang 还提交了一个 C 实现,尽管它预计在 Python 实现被证明足够有用值得优化之前不会被包含进来。
参考
版权
本文档已进入公有领域。
来源: https://github.com/python/peps/blob/main/peps/pep-0309.rst
最后修改时间: 2023-09-09 17:39:29 GMT