装饰器教学博客地址.
什么是装饰器
装饰器分成两部分来看,一是装饰,二是一个器.所谓器,就是一个函数.装饰,就是为其他函数添加附加功能,而且不丧失和修改原来函数的功能.
装饰器需要把握两个原则:
1 绝对不能去修改被修饰函数的源代码
2 不修改被修饰函数的调用方式
不满足这些条件的不是装饰器.
为什么要使用装饰器:
一般程序上线之后,不再应该修改程序的源代码,违反了开放-封闭原则.需要在不影响原函数的情况下,实现附加功能.
装饰器的原理
装饰器=高阶函数+函数嵌套+闭包
装饰器首先需要用到高阶函数的概念,接受一个函数,返回函数.但是仅用高阶函数是无法在不改变函数调用的情况下实现装饰效果.因此需要引入闭包的概念.
一个函数中定义了函数叫做函数嵌套.嵌套的函数,其内部包含的变量作用域(包括直接引入的外部局部变量)以及代码可以看成是一个包.如果将这个嵌套函数返回到外层,则这个函数及其变量会被一起打成一个封闭的包供使用.即使是在外部调用这个包,其内部的局部变量也还在生存,并不因为在外部调用而失去局部变量.这就是闭包的概念=外部函数的结果是返回内部函数.
闭包用在装饰器里,就既可以在函数内执行被装饰函数,又可以将被装饰函数的结果返回.
在使用完三个概念之后,最后要用装饰器的函数赋值给一个与被装饰函数同名的变量来更改被装饰函数名的引用,使之指向装饰器函数.被装饰函数的本体由于先在赋值右边进行运算,本体被装饰器函数拿到,所以不会被垃圾回收.现在执行被装饰函数的时候,实际上是指向高阶函数,返回闭包装饰函数,装饰函数根据传入的参数执行被装饰函数,执行其他功能,然后返回被装饰函数的结果.从结果来看,就是实现了不修改被装饰函数也不修改函数调用方式的装饰效果.
将装饰器函数赋给被装饰函数的名称的过程,可以用一个语法糖@装饰器名称写在被装饰函数定义之前来实现.
装饰器实现
# 典型的装饰器
def decor(func): # 装饰器函数定义,是一个高阶函数,参数是一个函数,就是被装饰函数
# 嵌套函数,闭包函数.会带着上一层传入的func和本包的全部变量进行打包,用于执行被装饰函数和其他功能,然后返回被装饰函数的值,其中的变量就是原本传入给被装饰函数的变量.
def wrapper(*args, **kwargs):
print('执行其他功能') # 执行其他功能的语句
res = func(*args, **kwargs) # 执行被装饰函数并且获得返回值放到一个变量里
return res # 返回被装饰函数执行后的值
return wrapper
# 随便写一个自己的函数
def my_func1(x, y, z):
return x + y + z
# 不用语法糖的方法
my_func1 = decor(my_func1)
'''先对右边进行运算,此时将my_func1函数对象传进decor函数,由于decor函数返回的是闭包函数wrapper(wrapper里包裹着传入给decor函数的my_func1).
所以这一步就等于用闭包函数覆盖了原来的被装饰函数,即my_func1wrapper,之后用和被装饰函数一样的调用方法来调用wrapper函数,wrapper函数将所有的
参数原样传递给已经被打包在里边的原来的my_func1函数,并且把额外的功能也一并执行,执行完以后只返回原来my_func1的执行结果.'''
a = my_func1(1, 2, 3)
print(a) # 输出结果,这样就会发现,除了和原来函数一样的使用方法之外,还额外执行了其他功能.
# 用语法糖的方法
@decor # 用@装饰器名称写在函数定义之前,相当于 my_func2 = decor(my_func2)
def my_func2(x, y, z):
return x * y * z
b = my_func2(1, 2, 3)
print(b)
带参装饰器
带参装饰器的原理就是在装饰器外边再套一个带参的函数外套,用于将参数广播到整个函数域,然后返回原来的装饰器函数.在语法糖的部分,由于外边又套了一个高阶函数,所以语法糖部分不能够直接等于函数名,而是要等于装饰器第一层函数执行后的部分,也就是仍然把原来的装饰器函数赋给被装饰函数名.
# 带参装饰器
def decor_arg(name):
def decor(func):
def wrapper(*args, **kwargs):
print('原来不加参数装饰器的功能')
print('传入参数的装饰器的参数是{}'.format(name))
res = func(*args, **kwargs)
return res
return wrapper
return decor
@decor_arg('minko') #这里实际上等于 my_func1 = decor_arg('minko'),也就是 my_func1 = decor,不同的是返回的闭包函数内已经带有了局部变量name,这样在装饰器内就可以做很多判断等事情.这里的本质还是函数柯里化的过程.
def my_func1(x, y, z):
return x + y + z
print(my_func1(3,4,5))
其他知识补充:解压序列
python支持用变量直接解包数据复制,用与序列类型相同数量的元素一次性分开取出序列内的所有元素,还可以用变量名代表任意数量,这时候变量会将匹配的元素变成一个列表.
a, b, c = [1, 2, 3]
print(a, b, c)
# 结果是1 2 3
a, b, c, d, e = 'hello'
print(a, b, c, d, e)
# 结果是h e l l o
a, *b, c, d = [1, 2, 3, 4, 5, 6, 7, 8, 9]
print(a, c, d)
# 结果是1 8 9
print(b)
# 结果是[2, 3, 4, 5, 6, 7]
a, *b = 'hello'
print(a, b)
# 结果是h ['e', 'l', 'l', 'o']
如果要交换两个变量,低级一些的语言需要用到临时变量,python可以直接交换:
# 交换a和b两个变量的值:
temp = a
a = b
b = temp
# python的语法糖
a,b = b,a