函数在python中是first-class对象。特别的,函数能够当作一个普通值传递给另一个函数或者从另一个函数返回得来。这比C/C++里的函数指针的传递更加方便。这里举个例子,这以一个函数作为参数,并返回该函数的名字:
>>> def show_func(f):
... print f.__name__
再举个例子以使用上面定义的函数:
>>> def foo():
... print 'foo here'
>>> show_func(foo)
foo
再看下面,它是一个函数,它创建一个新函数然后把它当作結果返回。在这种情况下,make_adder()创建了一个函数,这个函数的作用只是将参量加上一个常量。
>>> def make_adder(x):
... def adder(y):
... return x+y
... return adder
>>> a=make_adder(5)
>>> a(10)
15
其实感觉make_adder()就像一个函数工厂,专门负责产生函数。
把上面的例子好好想想,你会得到一个不错的想法,我们可以创建一类特别的函数,它们接受一个函数作为参数,并且返回一个新的函数,返回的函数与输入的参数没有关系,那么我们就完全改变了原来的函数的用途,而如果返回的函数就是传入的函数,那么其实我们什么也没有做。不过,大多数情况下,我们对某些功能函数有特别的要求,所以只是对原来的函数作一些小的动作,然后返回一个功能大致相似的函数。
举个例子吧,我们包装一个函数,使得对它的调用被记录到日志文件中去:
>>> def show_call(f):
... def shown(*args, **kwargs):
... print 'Calling', f.__name__
... return f(*args, **kwargs)
... return shown
>>> bar = show_call(foo)
>>> bar()
Calling foo
foo here
如果我们将show_foo()返回的值再次与传参名字进行绑定,那么我们很有效率地取得了一个原来函数的包装后版本:
>>> foo = show_call(foo)
>>> foo()
Calling foo
foo here
如果我们继续上面的讨论,其实你就已经理解了修饰器的核心本质,因为一个修饰器就是python语法上的甜味剂,正如上面写的例子那样,只是为美中不足做些弥补。不过,有简单语法可供使用:
@show_call
def foo():
pass
它就等同于下面的代码:
def foo():
pass
foo = show_call(foo)
任何函数,只要它接受一个函数参数,并返回一个函数,都能被当成修饰器来用。
事实上,这没有什么大不了的。修饰器并没有添加什么新的函数到python库中。它只是借用一种新的语法形式来实现一个古老的想法。但是修饰器是语法上的一大变革,它非常流行,而且广泛运用在现代的python代码中。
修饰器非常有用,它被广泛运用在需要代理一些函数实现各种不同功能的场合。函数前的修饰器,以一种独特的语法,使函数意图和功能更加明确。
当有一个修饰器显示在函数上头时,就表示原始函数与修饰之后的函数有了明显的不同:
>>> def bar():
... ''' function bar() '''
... pass
>>> bar.__name__, bar.__doc__, bar.__module__
('bar','function bar()','__main__')
>>> import inspect
>>> inspect.getargspec(bar)
([], None, None, None)
>>> bar2=show_call(bar)
>>> bar2.__name__, bar2.__doc__, bar2.__module__
('shown', None, '__main__')
>>> inspect.getargspec(bar2)
([],'args','kwargs',None)
从上面的例子可以看到,函数属性并没有拷贝给包装之后的函数,原函数的属性可以通过拷贝的方法,保留给包装后的函数。下面是一个更好的show_call():
def show_call(f):
def shown(*args, **kwds):
print 'Calling', f.__name__
return f(*args, **kwds)
shown.__name__ = f.__name__
shown.__doc__ = f.__doc__
shown.__module__ = f.__module__
shown.__dict__.update(f.__dict__)
return shown
改进后的版本,尽管属性正确了,但是其署名还是错误:
>>> bar2=show_call(bar)
>>> bar2.__name__, bar2.__doc__, bar2.__module__
('bar', ' Function bar() ', '__main__')
python2.5中出现的functools模块避免了你写上面冗长而无聊的代码,它提供了一个修饰器的修饰器,名叫wrap()。上面的例子可以写成如下形式:
>>> def show_call(f):
... @wraps(f)
... def shown(*args, **kwds):
... print 'Calling', f.__name__
... return f(*args, **kwds)
... return shown
>>> bar2=show_call(bar)
>>> bar2.__name__, bar2.__doc__, bar2.__module__
('bar', ' Function bar() ', '__main__')
你可能注意到上面那个所谓的修饰器的修饰器很奇怪,因为它居然接受一个参数。那它是怎么工作的呢?
想像我们有这样一段代码:
@wraps(f)
def do_nothing(*args, **kwds):
return f(*args, **kwds)
它实际等价于:
def do_nothing(*args, **kwds):
return f(*args, **kwds)
do_nothing = wraps(f)(do_nothing)
为了使上面的属性保留有意义,wraps()必须是一个修饰器工厂,这个工厂返回的值本身就是一个修饰器。很绕吧!总之,wraps()是一个返回值为函数的函数,返回的函数是一个以函数为参数并返回函数的函数。
举个简单的例子吧。我们希望一个修饰器反复调用它所修饰的函数。对于一个固定的调用次数,可以写成如下:
>>> def repeat3(f):
... def inner(*args, **kwds):
... f(*args, **kwds)
... f(*args, **kwds)
... return f(*args, **kwds)
... return inner
>>> f3=repeat3(foo)
>>> f3()
foo here
foo here
foo here
但是我们想传递一个参数控制反复调用的次数。这样我们需一个函数它的返回值是一个修饰器。这个返回的修饰器将与上面的repeat3()很相似。它需要另一层包装:
>>> def repeat(n):
... def repeatn(f):
... def inner(*args, **kwds):
... for i in range(n):
... ret = f(*args, **kwds)
... return ret
... return inner
... return repeatn
这里的repeat()就是一个修饰器工厂,repeatn()是实际上是一个修饰器,而inner()是被调用的包装函数。下面是使用这个修饰器的语法:
>>> @repeat(4)
... def bar():
... print 'bar here'
>>> bar()
bar here
bar here
bar here
bar here