把学习的东西都记一下
装饰器是python的重要部分,可以在不修改原函数代码的情况下,扩展原函数的功能,实现代码复用,切面编程。
python装饰器的实现基础
- 一切皆对象
#其实是生成了一个函数对象,func1是指向这个对象的引用 def func1(): print('this is func1') #又有一个引用func2指向这个函数对象 func2 = func1 print(id(func2)) #-->48715856 print(id(func1)) #-->48715856 del func1 func1() #Tip: Name 'func1' can be undefined func2() #-->this is func1
- 在函数中定义函数
def fun_out(): def fun_inner(): print('this is fun_inner') fun_inner() print('this is fun_out') fun_out() #-->this is fun_inner #-->this is fun_out #fun_inner在fun_out外部是无法访问的 fun_inner() #Tip: Unresolved reference 'fun_inner' fun_out.fun_inner() #Tip: Cannot find reference 'fun_inner' in 'function' fun_out().fun_inner() #Tip: Cannot find reference 'fun_inner' in 'None'
- 将函数作为返回值
def fun_out(): def fun_inner(): print('this is fun_inner') print('this is fun_out') return fun_inner fun = fun_out() fun() #-->this is fun_out #-->this is fun_inner
- 将函数作为参数传递
def fun_out(func_param): func_param() print('this is fun_out') def fun_log(): print('this is fun_log') fun_out(fun_log) #--> this is fun_log this is fun_out
正是因为python语言具有以上特性,才有了装饰器。
第一个Python装饰器
#这是我的原有函数,现在我要扩展它:
#在函数执行前后打印日志
def my_foo():
print('do something in foo')
#log_decorator就是一个装饰器
#它返回一个函数对象!!!-这很重要
def log_decorator(func):
def wrap_func():
print('will exec', func.__name__)
func()
print('done exec', func.__name__)
return wrap_func
#本质上来说,my_foo已经不再是原来的my_foo了,因为它指向了一个新的函数对象
#这个新的函数对象是log_decorator()返回的,它是wrap_func
my_foo = log_decorator(my_foo)
#但是我们并不管这些,因为同样是调用my_foo()
#它现在却可以打印:
# will exec my_foo
# do something in foo
# done exec my_foo
my_foo()
- 内置装饰器wraps
在上面的代码中,如果我们在my_foo()后面加上
print(my_foo.__name__)
就会发现打印出来的是wrap_func,这明显不行,我希望装饰器仅仅是扩展函数功能,而不要改变函数信息,python提供了内置装饰器wraps来解决这个问题:
def log_decorator(func):
@wraps(func) #就这一行代码,放在wrap_func上面即可
def wrap_func():
print('will exec', func.__name__)
func()
print('done exec', func.__name__)
return wrap_func
- 语法糖@
在上面的代码中,我们需要先执行一下 my_foo = log_decorator(my_foo) 才能得到一个装饰后的my_foo,难道每次执行my_foo()之前都要这样搞一下,这明显不行,python提供了一个语法糖@来解决这个问题
@log_decorator
def my_foo():
print('do something in foo')
这样,我们的第一个python装饰器就可以写成下面这种方式了:
def log_decorator(func):
@wraps(func)
def wrap_func():
print('will exec', func.__name__)
func()
print('done exec', func.__name__)
return wrap_func
@log_decorator
def my_foo():
print('do something in foo')
my_foo()
print(my_foo.__name__)
完成!跟内置装饰器staticmethod、classmethod都是一样的用法。
带参数的装饰器
首先说明一点:无参和有参的区别是@装饰器后面带不带(),而不是()里面有没有参数。
在上面的例子中,我们是直接把日志print出来了,如果我们想写入到指定的文件中呢?
可以这样:
def log_decorator(func, logfile=''):
@wraps(func)
def wrap_func():
print('will exec', func.__name__)
if logfile:
with open(logfile, 'a') as f:
f.write('will exec '+ func.__name__)
func()
print('done exec', func.__name__)
if logfile:
with open(logfile, 'a') as f:
f.write('done exec '+ func.__name__)
return wrap_func
def my_foo():
print('do something in foo')
my_foo = log_decorator(my_foo, logfile='d.log')
my_foo()
这样确实是可以,但是这样我们就用不了语法糖@了,因为@只会给装饰器传递一个参数--原函数名,所以为了使用@,我们就需要再把装饰器做一层包装
#这里log_decorator其实并不是真正的装饰器
#它只是一层包装,它返回装饰器real_decorator
def log_decorator(logfile=''):
#real_decorator才是真正的装饰器
def real_decorator(func):
@wraps(func)
def wrap_func():
print('will exec', func.__name__)
if logfile:
with open(logfile, 'a') as f:
f.write('will exec '+ func.__name__)
func()
print('done exec', func.__name__)
if logfile:
with open(logfile, 'a') as f:
f.write('done exec '+ func.__name__)
return wrap_func
return real_decorator
#log_decorator(logfile='a.log')返回的是real_decorator
#所以就相当于是 @real_decorator,real_decorator又定义在log_decorator的内部
#它自然可以使用log_decorator的参数,这样就实现了带参数的装饰器
@log_decorator(logfile='a.log')
def my_foo():
print('do something in foo')
my_foo()
多个装饰器的执行顺序
代码:
def decorator_a(func):
print('Get in decorator_a')
def inner_a(*args, **kwargs):
print('Get in inner_a')
return func(*args, **kwargs)
return inner_a
def decorator_b(func):
print('Get in decorator_b')
def inner_b(*args, **kwargs):
print('Get in inner_b')
return func(*args, **kwargs)
return inner_b
@decorator_b
@decorator_a
def f(x):
print('Get in f')
return x * 2
f(1)
输出:
Get in decorator_a
Get in decorator_b
Get in inner_b
Get in inner_a
Get in f
这里来分析一下:
@decorator_b
@decorator_a
def f(x):
print('Get in f')
return x * 2
f(1)
其实就是:
def f(x):
print('Get in f')
return x * 2
f = decorator_b(decorator_a(f))
f(1)
很明显:
decorator_a(f)最先执行,所以最先输出Get in decorator_a
然后执行decorator_b(...),输出Get in decorator_b
所以包装的时候是从里到外
执行的时候呢,f(1) ==> inner_b(inner_a(*args, **kwargs))
从日志可以看出,执行的时候是从外到里。
装饰器类
在上面的例子中,装饰器都是一个函数,其实也可以把一个类作为装饰器,其实原理都是一样的,无非就是要区分对象的创建(__init__)和对象的调用(__call__),这里就只上代码了:
- 无参装饰器类
class log_decorator(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print('will exec', self.func.__name__)
self.func(*args, **kwargs)
print('done exec', self.func.__name__)
@log_decorator
def my_foo():
print('do something in foo')
my_foo()
print(my_foo.__name__) #AttributeError: 'log_decorator' object has no attribute '__name__'
这种方式,my_foo其实就是log_decorator的一个实例,而且无法通过wraps还原信息。而且print(my_foo.__name__)是会抛出异常的,因为log_decorator并没有__name__属性。
- 有参装饰器类
class log_decorator(object):
def __init__(self, param1='', param2=''):
self.param1 = param1
self.param2 = param2
def __call__(self, func):
@wraps(func)
def wrap_func(*args, **kwargs):
print('will exec', func.__name__)
func(*args, **kwargs)
print('done exec', func.__name__)
return wrap_func
@log_decorator()
def my_foo():
print('do something in foo')
my_foo()
print(my_foo.__name__)
输出:
will exec my_foo
do something in foo
done exec my_foo
my_foo
my_foo还是它自己。