在 2.2 节函数声明中,DIP 讲述了关于函数定义的一些基础知识,后来又在 4.2 节使用可选参数和命名参数中讲述了关于函数参数的较为深入的话题。函数相关的内容分到两个不同的章节,DIP 的内容编排的确是出常人所料,不过今天我们不讨论这个,闲话少说,进入关于函数的更多正题。
在 C/C++ 中,不定参数可以算得上一节提高篇的课程。因为它的 va_list、va_start和 va_end 等是侵入式的,理解起来并不容易;此外由于 C/C++ 都是静态强类型语言,在运行时数据并不携带类型信息,因此不定参数函数更像是一个调用协议,需要函数定义者和使用者之间通过文档、注释等方式进行沟通;或者像 printf() 函数那样用 fmt 参数隐式指出参数类型,然后进行显式转型。
不定参数在 Python 中则简单得多。再回过头来年一下 C/C++,其实 va_list,完全是一个 tuple 实现,因为它可以持有不同数据类型的指针(通过void* 来实现)。得益于 Python 函数调用时的 boxing 和 unboxing 操作,Python 可以为不定参数的存取提供更为简洁的实现。如:
def foo(*args):
for arg in args: print arg
在 Python 中可以使用 *args 语法为函数定义不定参数,其中 args 相当于 C/C++ 的 va_list,它是一个 tuple 类型的参数容器,所以无需所谓的 va_start、va_end 就可以简单遍历所有参数了。
在 Python 中,不定参数可以直接用 tuple 参数调用,如:
names = ('laiyonghao', 'denggao', 'liming')
foo(*names) # 留意符号 *
尽管不定参数给函数带来了很多便利性,但 Python 的关键字参数尤为神通广大。关键字参数是指以下形式定义的参数:
def foo(**kw): pass
其中 kw 本质上是一个 dict 对象,所以可以这样调用 foo:
foo( **{'a' : 1, 'b' : 2, 'c' : 3} )
看起来有点眼熟?对的,在“第一贴”(http://blog.csdn.net/lanphaday/archive/2008/08/31/2857813.aspx)里 DIP 的例 2.1 就有这几行代码:
if __name__ == "__main__":
myParams = {"server":"mpilgrim",
"database":"master",
"uid":"sa",
"pwd":"secret"
}
print buildConnectionString(myParams)
这个 buildConnectionString(myParams) 和前文的 foo() 调用很像吧,而且利用关键字参数后更复杂了。其实不是这样的,如果使用关键字参数,例2.1 可以写得优为简洁:
def buildConnectionString(**params):
"""Build a connection string from a dictionary of parameters.
Returns string."""
return ";".join("%s=%s" % (k, v) for k, v in params.iteritems())
if __name__ == "__main__":
print buildConnectionString(
server = ‘mpilgrim’,
database = ‘master’
uid = ‘sa’
pwd = ‘secret’)
除了更加优雅外,也比以前一种写法提升性能呢。
在 C++ 中有一种对象被称为仿函数(functor),因为它重载了 operator(),参数模仿函数调用。得益于 inline 关键字,仿函数可以减少函数调用,提升性能,所以在 C++ 的世界里仿函数是非常重要的。
尽管 Python 中没有类似 inline 的关键字,但因为有不少函数需要自身保留一些“状态”,所以仍然实现了仿函数,在 Python 中称为可调用对象。
狭义来讲,可调用对象是拥有 __call__ 方法的类实例,如:
>>> class Callable(object):
... def __call__(self):
... print 'callable'
...
>>> call_obj = Callable()
>>> call_obj()
定义一个函数在大多数时候都很简单,但事实上 def 语句是相当复杂的,不信你看看它的语法:
funcdef ::= [decorators] "def" funcname "(" [parameter_list] ")" ":" suite
decorators ::= decorator+
decorator ::= "@" dotted_name ["(" [argument_list [","]] ")"] NEWLINE
dotted_name ::= identifier ("." identifier)*
parameter_list ::= (defparameter ",")*
( "*" identifier [, "**" identifier]
| "**" identifier
| defparameter [","] )
defparameter ::= parameter ["=" expression]
sublist ::= parameter ("," parameter)* [","]
parameter ::= identifier | "(" sublist ")"
funcname ::= identifier
是不是开始有懵的感觉了?
其实没有多大关系啦,大部分你都已经掌握了的,唯一可能还不太了解的可能就是 decorator(s),其实 decorator 的理念很简单:就是把一个函数“改头换面”之后返回给大家,让大家仍然可以用原来定义的函数名来调用这个返回的新函数。有点拗口?那看个例子就好了,比如有个王地主,出了名的小气,小气到鸡蛋过手都要小一圈:
>>> def wang(func):
... def my_func(n, w):
... if n == "wang":
... return func(n, w * 0.9)
... return func(n, w)
... return my_func
...
>>> @wang
... def transfer(name, weight):
... print "name = %s, weight = %s"%(name, str(weight))
...
>>> transfer("lai", 100.0)
name = lai, weight = 100.0
>>> transfer("wang", 100.0)
name = wang, weight = 90.0
怎么样,看看输出,有点神奇吧,我过手的鸡蛋还是 100 克的,王地主就不一样了,只剩 90 克,真是高招儿……
其实 decorator 的本事这止这些,下面的代码是从手册里抄出来的:
@f1(arg)
@f2
def func(): pass
上面的代码等效于:
def func(): pass
func = f1(arg)(f2(func))
怎么样?decorator 可带参数还可以嵌套,具体的用法就请 RTFM 了。最后透一点剧情,decorator 在 py3.0 中也开始可以用来修饰类的定义了,不过这个内容就等给 DIP 打 py3.0 的补丁的时候再谈了。
-------------------------------------------
咳,不好意思,时隔 8 个多月之后,《<Dive into Python>大补贴》终于推出第二贴。关于 DIP 的其它讨论请见这儿:http://blog.csdn.net/lanphaday/category/454256.aspx。