装饰器是程序开发中经常会用到的一个功能,功能就是在运行原来功能基础上,加上一些其它功能,比如权限的验证,比如日志的记录等等。不修改原来的代码,进行功能的扩展。
比如java中的动态代理,python的注解装饰器。
其实python的装饰器,是修改了代码。
装饰器的工作原理:
def outer(fn):
print('...outer...')
def inner():
print('...inner...')
fn()
return inner
def test():
print('...test...')
test = outer(test)
print('*'*15)
test()
结果如下:
装饰器原理结果.jpg
装饰器就是引用了闭包的概念。装饰器的工作原理如下:
装饰器原理.jpg
现在采用装饰器:
def outer(fn):
print('...outer...')
def inner():
print('...inner...')
fn()
return inner
@outer #就相当于 test = outer(test)
def test():
print('...test...')
test()
输出结果如下:
装饰器结果.jpg
多个装饰器:
def f1(fn):
def f1_inner():
return '<b>'+fn()+'</b>'
return f1_inner
def f2(fn):
def f2_inner():
return '<i>'+fn()+'</i>'
return f2_inner
@f1
@f2
def fun():
return 'hello world!'
print(fun())
结果如下:
多个装饰器结果.jpg装饰器主要的功能有:
引入日志
函数执行时间统计
执行函数前预备处理
执行函数后清理处理
权限检验等场景
缓存
之前演示的装饰器都是无参调用的,装饰器的传参是如何实现的:
def f1(fn):
def f2(a,b):
print(a,b)
fn(a,b)
return f2
@f1
def fun(a, b):
print(a+b)
fun(10, 20)
输出结果如下:
装饰器传参结果.jpg
这样把参数写死显然不适合装饰器的普遍性,如果有不同参数的函数调用该装饰器,如何改写装饰器:
from time import ctime, sleep
def f1(fn):
def f2(*args, **kwargs):
print('%s call at %s'%(fn.__name__, ctime()))
print(*args, **kwargs)
fn(*args, **kwargs)
return f2
@f1
def fun1(a, b):
print(a+b)
@f1
def fun2(a, b, c):
print(a+b+c)
fun1(10, 20)
sleep(2)
fun2(30, 40, 50)
结果如下:
不定长传参结果.jpg
以上举得被装饰器装饰过的函数都是直接打印,如果是想返回的值的函数使用该装饰器,那装饰器又不具备普遍性,修改装饰器如下:
from time import ctime, sleep
def f1(fn):
def f2(*args, **kwargs):
print('%s call at %s'%(fn.__name__, ctime()))
return fn(*args, **kwargs)
return f2
@f1
def fun1(a, b):
print(a+b)
@f1
def fun2(a, b, c):
return a+b+c
fun1(10, 20)
sleep(2)
print(fun2(30, 40, 50))
结果如下:
如果闭包中的f2没有return fn(args,kwargs)而是直接fn(args,**kwargs),最后打印得到的结果是None。
装饰器带参数,在原有装饰器的基础上,设置外部变量。
from time import ctime, sleep
def timefun_arg(pre="hello"):
def timefun(func):
def wrappedfunc():
print("%s called at %s %s"%(func.__name__, ctime(), pre))
return func()
return wrappedfunc
return timefun
@timefun_arg("wangcai")
def foo():
print("I am foo")
@timefun_arg("python")
def too():
print("I am too")
@timefun_arg()
def coo():
print("I am coo")
foo()
sleep(2)
too()
sleep(2)
coo()
输出结果如下:
装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重写了 call() 方法,那么这个对象就是callable的。
类装饰器:
class Dog():
def __init__(self, fun):
self.__fun = fun
def __call__(self, *args, **kwargs):
print('...call...')
return self.__fun(*args, **kwargs)
@Dog #foo = Dog(foo)
def foo():
print('...foo...')
@Dog
def foo1(a, b):
print('...foo1...')
return a+b
foo()
print(foo1(1,2))
结果如下:
类装饰器结果.jpg当用Dog来装作装饰器对foo函数进行装饰的时候,首先会创建Dog的实例对象
并且会把foo这个函数名当做参数传递到init方法中,即在init方法中的fun变量指向了foo函数体,foo函数相当于指向了用Dog创建出来的实例对象。当在使用foo()进行调用时,就相当于让这个对象(),因此会调用这个对象的call方法。为了能够在call方法中调用原来foo指向的函数体,所以在init方法中就需要一个实例属性来保存这个函数体的引用,所以才有了self.__fun = fun这句代码,从而在调用call方法中能够调用到foo之前的函数体。
网友评论