是时候写一下Python装饰器了。
装饰器记录好,就剩下多进程,多线程,携程了,前面还欠着re,不知道什么时候写,re实在太强大了。
装饰器,记得第一次看到啥都不懂,也不知道为什么要设计这玩意,后面接触了Flask,Django才知道那些所谓的中间件都是一些装饰器。
首先,Python是万物皆对象,所以一个函数作为对象实在太正常,但刚接触Python的时候,我至少很长时间不能接受这种行为。
首先上一个最简单的装饰器:
# -*- coding:utf-8 -*- import random def check(func): print('I am func,I will start first') def wrap(*args, **kwargs): res = func(*args, **kwargs) if res > 0: return res else: return 0 print('I go out') return wrap @check def foo(m, n ): return random.randint(m, n)
这是一个极其普通的装饰器,就是负数输出为0。
@check所谓的语法糖很有意思,你不执行check或者foo的任意一个函数,单还是会输出
I am func,I will start first I go out
这个说明check函数已经执行了,check函数返回的是另一个需要被装饰的函数wrap。这里面到底发生了什么。
当用了@check的语法糖以后,其实在执行该脚本时,Python内部先执行了foo = check(foo)
所以,check函数会执行,而且foo也变成了check函数执行返回的wrap.
print(foo.__name__)
wrap
在前面代码中,如果打印foo的名字代码,就会发现foo的名字都已经变了。
现在我给foo与wrap都加上一些docstring的说明。
import random def check(func): print('I am func,I will start first') def wrap(*args, **kwargs): '''My name is wrap,hello''' res = func(*args, **kwargs) if res > 0: return res else: return 0 print('I go out') return wrap @check def foo(m, n ): '''My name is foo,hello''' return random.randint(m, n) print('foo_docstring---->',foo.__doc__)
foo_docstring----> My name is wrap,hello
从执行可以看出foo的docstring都已经发生了改变,这样对原函数实在太不友好了,不是说装饰器不影响原函数的执行,单原函数的一些内部属性发送了变化,装饰器你也太霸道了。
Python想到了这些,有一个原装的模块可以解决。
import random
import functools
def check(func):
print('I am func,I will start first')
@functools.wraps(func)
def wrap(*args, **kwargs):
'''My name is wrap,hello'''
res = func(*args, **kwargs)
if res > 0:
return res
else:
return 0
print('I go out')
return wrap
@check
def foo(m, n ):
'''My name is foo,hello'''
return random.randint(m, n)
print('foo_docstring---->',foo.__doc__)
print(foo(2, 6))
print('foo_name---->',foo.__name__)
foo_docstring----> My name is foo,hello 3 foo_name----> foo
从输出可以看出foo的内部属性在装饰器装饰后,没有发生任何改变。
接下来,我说一下多层装饰器,理解了这个多那些框架的中间件应该有更好的理解,至少我是这么认为的。
多层装饰器就是有多个@,这个多层装饰器,其实有那么一点点递归的感觉。
def deco1(func): print("This is deco1") def wrap1(*args, **kwargs): print('enter deco1:') result = func(*args, **kwargs) print('exit deco1') return result return wrap1 def deco2(func): print("This is deco2") def wrap2(*args, **kwargs): print('enter deco2') result = func(*args, **kwargs) print('exit deco2') return result return wrap2 def deco3(func): print("This is deco3") def wrap3(*args, **kwargs): print('enter deco3') result = func(*args, **kwargs) print('exit deco3') return result return wrap3 @deco1 @deco2 @deco3 def foo2(x, y): return x ** y print('_' * 100) foo2(3, 4)
This is deco3 This is deco2 This is deco1 ____________________________________________________________________________________________________ enter deco1: enter deco2 enter deco3 exit deco3 exit deco2 exit deco1
上面的代码做的是一个三层的装饰的,首先Python还是根据语法糖,分别执行了三个装饰器,所以先输出了三个装饰函数里面的打印输出。
从初始化来看,装饰器初始化是从下到上的。再来分析一下,装饰器在执行装饰器函数的时候,做了什么赋值。
下面是啰嗦的解释,不想看啰嗦的解释,应该foo2 = deco1(deco2(deco3(foo2)))
初始化过程中,首先foo2被装饰
foo2 = deco3(foo2)的返回函数wrap3
接下来:wrap3的函数继续传递给deco2
foo2 = deco2(wrap3)返回函数wrap2
再接下来:wrap2的函数继续传递给deco1
foo2 = deco1(wrap2)返回函数wrap1
所以初始化的最后,foo2变成了wrap1
所以在执行foo2()的过程,就变成了先执行wrap1,执行wrap1就要执行wrap1里面的func为wrap2,执行wrap2就是执行wrap2里面的wrap3,最后wrap3执行它里面的foo2函数。
但foo2函数执行后,从wrap3开始执行返回,通过result传递,最后传递到wrap1的result,为了执行foo,其实装饰器做了很多,感觉很绕。
很多框架的多层装饰器也差不多,为了判断你的函数能否执行,前面加了很多装饰器来帮助过滤参数,当你执行函数以后,也可以通过向外传递的过程中,设置一些条件,来限制最终的函数执行结果能不能传递出去。其实这也是我为什么现在不急着上手Django,Flask的原因,希望学好了基础,再去学那些框架,不仅知道如何用,更加知道为什么这么用,这样我觉的学起来会轻松很多。
再来一个带参数的装饰器,带参数的装饰器本质就是一个装饰器的生产机器,就是再装饰器的外面又套了一层函数,只不过返回了是这个装饰器。
def sum_rand(n): def lll(func): def wrap(*args, **kwargs): c = 0 for i in range(n): c += func(*args, **kwargs) return c return wrap return lll @sum_rand(90000) # 执行了生成一个lll的装饰器 def fxx(x, y): return x * y print(fxx(2, 3))
上面是一个带参数的装饰器,返回的是一个两数乘积累加多次的和。
@sum_rand(90000)就是一个装饰器的生产器
其实@sum_rand(90000)就是lll装饰器函数,只不过为什么需要读取他的参数,套在装饰器的外层,然后让它返回一个装饰器。
写到这里函数的装饰器差不多就这些,回头来看,其实也就那么一回事,下一个章节,我会记录一下类的装饰器。