【3.11】理解Python装饰器

python装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

一、通过案例来理解装饰器

1.1最简单的函数,准备附加额外功能

# -*- coding:gbk -*-
'''示例1: 最简单的函数,表示调用了两次'''
 
def myfunc():
	print("myfunc() called.")
 
myfunc()
myfunc()

1.2使用装饰函数在函数执行前和执行后分别附加额外功能

# -*- coding:gbk -*-
'''示例2: 替换函数(装饰)
装饰函数的参数是被装饰的函数对象,返回原函数对象
装饰的实质语句: myfunc = deco(myfunc)'''
 
def deco(func):
	print("before myfunc() called.")
	func()
	print("  after myfunc() called.")
	return func
 
def myfunc():
	print(" myfunc() called.")
 
myfunc = deco(myfunc)
 
myfunc()
myfunc()

1.3使用语法糖@来装饰函数

# -*- coding:gbk -*-
'''示例3: 使用语法糖@来装饰函数,相当于“myfunc = deco(myfunc)”
但发现新函数只在第一次被调用,且原函数多调用了一次'''
 
def deco(func):
	print("before myfunc() called.")
	func()
	print("  after myfunc() called.")
	return func
 
@deco
def myfunc():
	print(" myfunc() called.")
 
myfunc()
myfunc()

1.4使用内嵌包装函数来确保每次新函数都被调用

# -*- coding:gbk -*-
'''示例4: 使用内嵌包装函数来确保每次新函数都被调用,
内嵌包装函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数对象'''
 
def deco(func):
	def _deco():
		print("before myfunc() called.")
		func()
		print("  after myfunc() called.")
		# 不需要返回func,实际上应返回原函数的返回值
	return _deco
 
@deco
def myfunc():
	print(" myfunc() called.")
	return 'ok'
 
myfunc()
myfunc()

1.5对带参数的函数进行装饰

# -*- coding:gbk -*-
'''示例5: 对带参数的函数进行装饰,
内嵌包装函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数对象'''
 
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
 
myfunc(1, 2)
myfunc(3, 4)

1.6对参数数量不确定的函数进行装饰

# -*- coding:gbk -*-
'''示例6: 对参数数量不确定的函数进行装饰,
参数用(*args, **kwargs),自动适应变参和命名参数'''
 
def deco(func):
	def _deco(*args, **kwargs):
		print("before %s called." % func.__name__)
		ret = func(*args, **kwargs)
		print("  after %s called. result: %s" % (func.__name__, ret))
		return ret
	return _deco
 
@deco
def myfunc(a, b):
	print(" myfunc(%s,%s) called." % (a, b))
	return a+b
 
@deco
def myfunc2(a, b, c):
	print(" myfunc2(%s,%s,%s) called." % (a, b, c))
	return a+b+c
 
myfunc(1, 2)
myfunc(3, 4)
myfunc2(1, 2, 3)
myfunc2(3, 4, 5)

1.7让装饰器带参数

# -*- coding:gbk -*-
'''示例7: 在示例4的基础上,让装饰器带参数,
和上一示例相比在外层多了一层包装。
装饰函数名实际上应更有意义些'''
 
def deco(arg):
	def _deco(func):
		def __deco():
			print("before %s called [%s]." % (func.__name__, arg))
			func()
			print("  after %s called [%s]." % (func.__name__, arg))
		return __deco
	return _deco
 
@deco("mymodule")
def myfunc():
	print(" myfunc() called.")
 
@deco("module2")
def myfunc2():
	print(" myfunc2() called.")
 
myfunc()
myfunc2()

1.8让装饰器带 类 参数

# -*- coding:gbk -*-
'''示例8: 装饰器带类参数'''
 
class locker:
	def __init__(self):
		print("locker.__init__() should be not called.")
		 
	@staticmethod
	def acquire():
		print("locker.acquire() called.(这是静态方法)")
		 
	@staticmethod
	def release():
		print("  locker.release() called.(不需要对象实例)")
 
def deco(cls):
	'''cls 必须实现acquire和release静态方法'''
	def _deco(func):
		def __deco():
			print("before %s called [%s]." % (func.__name__, cls))
			cls.acquire()
			try:
				return func()
			finally:
				cls.release()
		return __deco
	return _deco
 
@deco(locker)
def myfunc():
	print(" myfunc() called.")
 
myfunc()
myfunc()

1.9装饰器带类参数,并分拆公共类到其他py文件中,同时演示了对一个函数应用多个装饰器

# -*- coding:gbk -*-
'''mylocker.py: 公共类 for 示例9.py'''
 
class mylocker:
	def __init__(self):
		print("mylocker.__init__() called.")
		 
	@staticmethod
	def acquire():
		print("mylocker.acquire() called.")
		 
	@staticmethod
	def unlock():
		print("  mylocker.unlock() called.")
 
class lockerex(mylocker):
	@staticmethod
	def acquire():
		print("lockerex.acquire() called.")
		 
	@staticmethod
	def unlock():
		print("  lockerex.unlock() called.")
 
def lockhelper(cls):
	'''cls 必须实现acquire和release静态方法'''
	def _deco(func):
		def __deco(*args, **kwargs):
			print("before %s called." % func.__name__)
			cls.acquire()
			try:
				return func(*args, **kwargs)
			finally:
				cls.unlock()
		return __deco


# -*- coding:gbk -*-
'''示例9: 装饰器带类参数,并分拆公共类到其他py文件中
同时演示了对一个函数应用多个装饰器'''
 
from mylocker import *
 
class example:
	@lockhelper(mylocker)
	def myfunc(self):
		print(" myfunc() called.")
 
	@lockhelper(mylocker)
	@lockhelper(lockerex)
	def myfunc2(self, a, b):
		print(" myfunc2() called.")
		return a + b
 
if __name__=="__main__":
	a = example()
	a.myfunc()
	print(a.myfunc())
	print(a.myfunc2(1, 2))
	print(a.myfunc2(3, 4))

二、内置装饰器

内置的装饰器有三个,分别是staticmethod、classmethod和property,作用分别是把类中定义的实例方法变成静态方法、类方法和类属性。由于模块里可以定义函数,所以静态方法和类方法的用处并不是太多,除非你想要完全的面向对象编程。而属性也不是不可或缺的,Java没有属性也一样活得很滋润。

三、几个好玩的例子

1.1 加标签

@makebold
@makeitalic
def say():
   return "Hello"
打印出如下的输出:

<b><i>Hello<i></i></i></b>
你会怎么做?最后给出的答案是:

def makebold(fn):
	def wrapped():
		return "<b>" + fn() + "</b>"
	return wrapped
 
def makeitalic(fn):
	def wrapped():
		return "<i>" + fn() + "</i>"
	return wrapped
 
@makebold
@makeitalic
def hello():
	return "hello world"
 
print hello() ## 返回 <b><i>hello world</i></b>

2.计时器

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()

函数参数未知

def timeit(func):
	def wrapper(*args, **kwargs):
		start = time.clock()
		ret = func(*args, **kwargs)
		end = time.clock()
		logger.warn('used time %s' % (func.__name__,(end - start)))
		return ret
	return wrapper

@timeit
def test(dd):
	print dd

3.打印日志

def log(func):
	def wrapper(*args, **kw):
		print 'call %s():' % func.__name__
		return func(*args, **kw)
	return wrapper

@log
def now():
	print '2013-12-25'

>>> now()
call now():
2013-12-25

更高级一点得写法

def log(text):
	def decorator(func):
		def wrapper(*args, **kw):
			print '%s %s():' % (text, func.__name__)
			return func(*args, **kw)
		return wrapper
	return decorator

@log('execute')
def now():
	print '2013-12-25'

>>> now()
execute now():
2013-12-25

3.显示程序运行状态

def show_status(arg):
	def _show_status(func):
		def __show_status(*args,**kwargs):
			print "%s:%s"%(arg,func.__name__)
			ret =func(*args,**kwargs)
			return ret
		return __show_status
	return _show_status

参考资料:

http://pythonmap.iteye.com/blog/1682696

http://www.cnblogs.com/rhcad/archive/2011/12/21/2295507.html#3336231

http://www.cnblogs.com/rollenholt/archive/2012/05/02/2479833.html

药企,独角兽,苏州。团队长期招人,感兴趣的都可以发邮件聊聊:tiehan@sina.cn
个人公众号,比较懒,很少更新,可以在上面提问题,如果回复不及时,可发邮件给我: tiehan@sina.cn