怎么样使用Python Cookbook将参数与函数关联起来?
Recipe 16.4. Associating Parameters with a Function (Currying)
Recipe 16.4. 将参数与函数关联起来(Currying)
(译注:本条目中出现“parameter”和“argument”两种字眼。其中的“parameter”似乎并不强调“形参”意味,故此处译为“参数”;“argument”则译为“实参”,敬请注意辨析。)
Credit: Scott David Daniels, Nick Perkins, Alex Martelli, Ben Wolfson, Alex Naanou, David Abrahams, Tracy Ruggles
[问题]
您需要将一个函数或其他可调用体(callable)包裹起来,得到另一个所需实参更少的可调用体;对于被包裹的函数(或其他可调用体)所需的其他实参,总是保留固定的值参与对新的可调用体的调用。此即:您希望 curry 一个可调用体,以便构造另一个可调用体来进行使用。
(译注:这段话原文精炼,重新转述:将一个可调用体包裹起来,构造一个新的可调用体,经由这个新的可调用体来间接调用被包裹的可调用体;新的可调用体的参数就是被包裹的可调用体的一部分参数,而被包裹的可调用体的另一部分参数值即实参被固定下来保持不变。)
[解法]
Curry(译注:原词有“咖喱”的含义)并不只是亚洲食系里面的美味调料——它还是 Python 及其他语言里的重要编程技巧:
# 译注:此系以 closure 方式实现的 currying;
# 还有其他实现方式,比如以 class 来实现等。
def curry(f, *a, **kw):
def curried(*more_a, **more_kw):
return f(*(a+more_a), **dict(kw, **more_kw))
return curried
[讨论]
Currying 是 functional programming 中的常用技巧,其将一个函数的某些实参与该函数绑定起来,等之后其他的实参到位了再一起使用。Currying 是为了向数学家 Haskell Curry 致敬而得名,这位数学家为形式化系统和过程的理论奠下了基石。一些学究(不妨勉强承认,他们的见解还是有一定道理的)声称,本条目所展现的技巧应该被称为“partial application”,而“currying”则是另外一种技术。然而,无论学究们是对是错,本书是 cookbook(食谱),在标题里用了“curry(咖喱)”这样的字眼并无大碍罢。况且,本条目支持“把 curry 当作动词使用”,这也是广大程序员们最普遍的作法。
本条目所定义的 curry 函数经由一个可调用体及其部分或全部实参来唤起。(有些人喜欢将“接收函数对象作为实参,并将新的函数对象作为返回结果”的函数称为“higher-order functions”。)Curry 函数返回一个被 curry 过的 closure ,其将后续给出的参数值作为实参来调用原始的可调用体。例如:
double = curry(operator.mul, 2)
triple = curry(operator.mul, 3)
实现 currying 的可选方案包括:closure、带有可调用实体的类,或者 lambda 。使用 closure 最简单、高效,故本条目采用了这种实现方案。
Curry 的典型用途是为 GUI 操作建构回调函数。在“不值得将某些操作归纳为一个新函数”的情况下,curry 就可以用来创建这些轻小的无名函数。例如,Tkinter Button 里的 command 就属于这种情况:
self.button = Button(frame, text='A', command=curry(transcript.append, 'A'))
Recipe 11.2 运用了 curry 功能特定的子集,用于产生无实参的可调用体——形如上例的 GUI 回调函数情形当中,经常需要用到这种无实参可调用体。然而较之 recipe 11.2 的 curry 函数,本条目提供的 curry 函数可伸缩性更强,且在复杂度和性能上无任何实质性额外消耗。
Currying 还可用于调试,为您的函数设定一些缺省的实参,或者为您当前的调试状态设定一些初始参数。例如,调试数据库程序的时候可以先设定:
Connect = curry(ODBC.Connect, dsn='MyDataSet')
Curry 在调试当中的另一种用法是将方法(methods)包裹起来:
def report(originalFunction, name, *args, **kw):
print "%s(%s)"%(name, ', '.join(map(repr, args) +
[k+'='+repr(kw[k]) for k in kw])
result = originalFunction(*args, **kw)
if result: print name, '==>', result
return result
class Sink(object):
def write(self, text): pass
dest = Sink( )
dest.write = curry(report, dest.write, 'write')
print >>dest, 'this', 'is', 1, 'test'
若您要创建 curry 函数供经常使用,采用 def fun 形式的函数定义方式可读性会更好,也更容易扩展。如您在 curry 代码实现中所见,curry 不外乎利用提供的参数定制函数,这其中并无神奇之处。当您感到采用 curry 会使代码更清晰的时候,才应该采用 curry 。Curry 用法通常是在强调“您想为经常使用的函数提供一些预先固定好的参数”,而不是想提供任何独立的操作过程。
Currying 也能用于创建“轻量级 subclass”。您可以对类的构造函数进行 curry ,以此来制造“subclass”假象:
BlueWindow = curry(Window, background="blue")
BlueWindow.__class__ 仍然是 Window ,而不是 subclass 。如果您只是想改变缺省的参数,而不是改变类的行为,那么 currying 可能比 subclassing 更适用(尽管这一看法尚持争议)。您仍然可以向经过 curry 的构造函数传递额外的参数。
编码实现 curry 时必须考虑两个问题,因为 positional arguments 和 keyword arguments 都可能在两个不同的时刻到来:a)进行 currying 的时刻;b)实际调用的时刻。这两个问题分别是:
1)调用时刻的 positional arguments 是先于还是后于 currying 时刻的 positional arguments ?
2)调用时刻的 keyword arguments 是否覆写 currying 时刻的 keyword arguments ,亦或是反之?
若您已看过本条目的“解法”栏目,可知笔者已经针对这两个问题作了特定的选择(同时也是最通用的方案):调用时刻的 positional arguments 放在 currying 时刻的 positional arguments 之后;调用时刻的 keyword arguments 覆写 currying 时刻的 keyword arguments 。在某些圈子里,这种方案被称作“left-left partial application”。不难写出针对其他选择所导致的变体,比如 right-left partial application :
def rcurry(f, *a, **kw):
def curried(*more_a, **more_kw):
return f(*(more_a+a), dict(kw, **more_kw))
return curried
如您所见,无论是用何种华丽词藻来称呼,right-left 方案只不过是采用了“more_a+a”的连接方式而已(left-left 方案是“a+more_a”);同样,对于 keyword arguments ,若您是希望 currying 时刻的覆写调用时刻的 keyword argument ,只需要将调用写成 **dict(more_kw, **kw) 即可。
只要您想,您还可以将原始函数的 docstring 复制给 curry 出来的函数,甚至连原始函数的名称也可以复制过来使用(在 Python 2.4 中容易实现,甚至在 Python 2.3 中也可以通过调用 new.function 来达成,详见 Recipe 20.1 的 sidebar 说明文字)。然而,笔者决定不这样做,因为原始名称以及 docstring 中的实参说明对于 curry 出来的新函数而言可能并不适用。为 curry 出来的新函数构建实际的签名式(signature)和文档说明也是可以做到的(利用标准库的 inspect 模块来达成),但比起区区四行简练悦目的 curry 实现代码来,做这些事情实在划不来!笔者坚决就此罢休。
有一种特定情形可能值得提醒:当您想要 curry 的可调用体是 Python 函数(而不是 bound method、C 语言代码实现的函数、可调用的 class instance,等等)的时候,您只需要对第一个参数进行 curry 就可以了。这种情况下,使用函数对象的 __get__ 方法可能就足够了。__get__ 接收一个任意实参并返回 bound-method 对象,其中的这个实参绑定了第一个参数。例如:
>>> def f(adj, noun='world'):
... return 'Goodbye, %s %s!' % (adj, noun)
...
>>> cf = f._ _get_ _('cruel')
>>> print cf( )
Goodbye, cruel world!
>>> cf
<bound method ?.f of 'cruel'>
>>> type(cf)
<type 'instancemethod'>
>>> cf.im_func
<function f at 0x402dba04>
>>> cf.im_self
'cruel'
[请参见]
Recipe 11.2 展示了 curry 功能的一个特定子集,专门用于 GUI 回调;inspect 模块和 dict 内建类型的文档参见 Library Reference 及 Python in a Nutshell 一书。
本文地址:http://www.45fan.com/dnjc/67339.html