python装饰器
首先要明白的一点是,装饰器装饰的是函数。python之中为什么会有这样的需求?
需求是怎么来的
def foo():
print 'in foo()'
foo()
这是一个函数,如果需要对这个函数增加一些功能,比如需要计算这个函数运行所消耗的时间。
import time
def foo():
start = time.clock()
print 'in foo()'
end = time.clock()
print 'used:', end - start
foo()
如果其他函数也需要计算运行时间呢?复制代码?DRY,所以可以将计算函数运行时间的功能抽象出来。
import time
def foo():
print 'in foo()'
def timeit(func):
start = time.clock()
func()
end =time.clock()
print 'used:', end - start
timeit(foo)
嗯,看上去挺不错的。不过这里的弱点是修改了调用部分的代码,关键是有的时候如果调用的函数是库函数的话就麻烦了。
所以这个时候想到的就是对foo进行包装,将timeit编写为对foo的包装函数,传入foo之后对foo进行加强,返回一个新的foo。
#-*- coding: UTF-8 -*-
import time
def foo():
print 'in foo()'
# 定义一个计时器,传入一个函数,并返回另一个附加了计时功能的函数
def timeit(func):
# 定义一个内嵌的包装函数,给传入的函数加上计时功能的包装
def wrapper():
start = time.clock()
func()
end =time.clock()
print 'used:', end - start
# 将包装后的函数返回
return wrapper
foo = timeit(foo)
foo()
PS:在在这个例子中,函数进入和退出时需要计时,这被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。与传统编程习惯的从上往下执行方式相比较而言,像是在函数执行的流程中横向地插入了一段逻辑。在特定的业务领域里,能减少大量重复代码。
问题:如果foo带参数怎么办?
很简单,装饰器之中的包装函数的参数和被装饰的函数的参数需要统一。
def deco(func):
def _deco(a, b):
print("before myfunc() called.")
ret = func(a, b)
print(" after myfunc() called. result: %s" % ret)
return ret
return _deco
@deco
def myfunc(a, b):
print(" myfunc(%s,%s) called." % (a, b))
return a + b
再举一个例子是Django之中的Signal装饰器:
def receiver(signal, **kwargs):
def _decorator(func):
if isinstance(signal, (list, tuple)):
for s in signal:
s.connect(func, **kwargs)
else:
signal.connect(func, **kwargs)
return func
return _decorator
语法糖
Python对装饰器提供了语法糖:
import time
def timeit(func):
def wrapper():
start = time.clock()
func()
end =time.clock()
print 'used:', end - start
return wrapper
@timeit
def foo():
print 'in foo()'
foo()
内置装饰器
内置的装饰器有三个,分别是staticmethod、classmethod和property。作用分别是把类中定义的实例方法变成静态方法、类方法和类属性。
class Rabbit(object):
def __init__(self, name):
self._name = name
@staticmethod
def newRabbit(name):
return Rabbit(name)
@classmethod
def newRabbit2(cls):
return Rabbit('')
@property
def name(self):
return self._name
示例比较简单,其中的函数是可以进行扩展的。这里@property定义的是一个只读属性,如果需要可写,则需要再一定一个setter:
@name.setter
def name(self,name):
self._name = name