#1.什么是闭包 装饰器的一个基本原理就是闭包,函数嵌套:
def func1(x):
return x
def func2(y):
print y
a = func1(func2)
a('hello')
输出为:
hello
上面的代码中,func1直接返回x,那么a=func2(),然后执行a('hello')等于直接执行func2("hello")
#2.基本函数装饰器入门 而装饰器就是在上面的闭包上实现的一个为函数添加功能的快捷方式: 先看代码:
def printmessage(dosth):
def wrapper(number):
print 'we print pow(number,2)'
return dosth(number)
return wrapper
@printmessage
def dosomething(number):
return pow(number, 2)
print dosomething(2)
第一,printmessage函数作为一个dosomething额外的功能用于打印消息,它的形式参数是目标函数
第二,wrapper的形参为printmessage提供参数,同时额外的功能在wrapper里实现,最后要返回wrapper(注意不是wrapper())
然后定义dosomething函数,并通过@使用printmessage这个装饰器
输出的结果为:
we print pow(number,2)
4
实际上,装饰器的作用只是为了解决这一步:
dosomething = printmessage(dosomething)
print dosomething(2)
等号左边的dosomething是添加了输出功能的版本,而括号里的dosomething是最初定义的函数 之所以用装饰器,主要是为了减少代码重构时的修改,不用每一处都像上面一样用赋值写,只需要 在原来的函数上添加个@符号和对应的函数作为装饰器即可
#3.带参数的装饰器与不确定的参数 在上面的例子里,我没法选择输入值作为打印内容,如果我想打印的是其他内容而不是'we print pow(number,2)'
同时,上面的装饰器函数的形参只有一个,如果dosomething是多个形参,就无法使用了,那么解决方法是:
def printmessage(message): #提供外部配置参数
def getmessage(func): #提供具体的函数功能
def wrapper(*args, **kwargs): #为具体的函数功能提供参数
print message
return func(*args, **kwargs)
return wrapper
return getmessage
#@printmessage('fuck')
def dosth(number, number1):
return pow(number, number1)
a=printmessage('fuck')
print a
dosth = a(dosth)
print dosth(2, 4)
问题1解决:通过多一层的嵌套,printmessage的形参是一个message,作为输出内容,同时,原来的使用函数作为形参的部分则交给内部的getmessage,事实上,如果只用闭包,也是外层提供不同的参数,里层提供函数的核心。因此getmessage的形参是func
问题2解决:wrapper函数的形参写成了*args和**kwargs,这样就可以满足不同形参数的函数的装饰需求,同时wrapper的形参负责为形参函数提供具体的参数,最后别忘了嵌套返回
参考使用不好的写法,可以看到闭包是怎么做的:
a=printmessage('fuck')
dosth = a(dosth)
首先是用printmessage输入参数,然后a=getmessage(),dosth=wrapper()
不过个人认为,添加的额外功能应该放在getmessage里,而wrapper只负责func的return,这样代码会更规范易懂
#4.docstring遗失? 另外,参考上面的代码,如果我们在dosth中加入docstring的话会怎样呢:
def printmessage(dosth):
def wrapper(number):
print 'we print pow(number,2)'
return dosth(number)
return wrapper
@printmessage
def dosomething(number):
"""
Fuck the numbers
:param number:
:return:
"""
return pow(number, 2)
print dosomething(2)
print dosomething.__doc__
计算结果其实是:
we print pow(number,2)
4
None
意味着这种情况下,原函数的docstring还有__name__都被遗失了
这种情况下需要from functools import wraps,然后再wrapper前调用@wraps(dosth),复制dosomething的docstring过来
from functools import wraps
def printmessage(dosth):
@wraps(dosth)
def wrapper(number):
print 'we print pow(number,2)'
return dosth(number)
return wrapper
再次执行:
we print pow(number,2)
4
Fuck the numbers
:param number:
:return:
这样就能看到原函数的docstring了
-------------------------------------------------------分割线------------------------------------------------------------------------------------------------------------------------------------------
#5.类装饰器 上面的是函数装饰器,那么还有类的装饰器:
class Foo(object):
def __init__(self, func):
self._func = func
def __call__(self):
print ('class decorator runing')
self._func()
print ('class decorator ending')
@Foo
def bar():
print ('bar')
bar()
输出结果为:
class decorator runing
bar
class decorator ending
只要实现了__call__,就可以变成一个可以调用的对象:
class Edu(object):
def __call__(self, *args, **kwargs):
return "yeshi1"
p = Edu()
print p()
#显示“yeshi1”
上面的代码是实例化了Edu类,然后调用p()时就会自动执行我们的代码,return一个值
同样,在上面的Foo类中,定义了__call__函数,于是Foo的实例就是可以调用的,调用时会自动执行func() 实际上,去掉@Foo,代码应该是这样的:
class Foo(object):
def __init__(self, func):
self._func = func
def __call__(self):
print ('class decorator runing')
self._func()
print ('class decorator ending')
def bar():
print ('bar')
bar = Foo(bar)
bar()
这个似乎跟函数式的装饰器差不了多少,至少在调用闭包时没什么区别,用了bar函数来实例化Foo类并赋值给bar,bar的执行时就会打印那些信息。看来重点是实现__call__,而其他功能通过其他成员函数实现。
#6.函数装饰器+类参数 而我们还可以搭配函数装饰器和类使用,请参考此例子:
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()
上面的代码通过实现类的静态方法,在不需要实例化的情况下调用上锁和释放的功能,并通过嵌套函数实现带参装饰器,而装饰器形参为包含静态方法的类
#7.其他东西 python还有些内置的装饰器:@staticmathod、@classmethod、@property
分别是静态方法、类方法和属性装饰器,平常静态方法用得更多,但是在设计模式上回用到classmethod等,详细的介绍可以参考:
classmethod:classmethod
property:property
而关于多重装饰器的使用:
@a
@b
@c
def f ():
这个代码等效于:
f = a(b(c(f)))
reference:
装饰器