代码编写原则
- 开放封闭原则
- 封闭。不允许对已实现的功能修改
- 开放。允许对已实现的功能进行拓展
- 高内聚低耦合原则
- 高内聚。针对的是模块内部,模块内的元素要密切一点,一般一个模块做一个功能
- 低耦合。针对的是模块之间,模块之间的关联度要少一点
装饰器的定义
在不改变函数的定义和调用的前提下,给函数扩展功能,这就是装饰器
装饰器的格式
@装饰器函数
def 被装饰的函数
装饰器的本质
本质是闭包的外层函数调用了被装饰的的函数
闭包的概念及使用,见本人博客中的另一篇文章 Python高级之闭包
装饰器的实现逻辑
demo:
def w1(func):
def inner():
# 验证1
# 验证2
# 验证3
func()
return inner
@w1
def f1():
print('f1')
python解释器就会从上到下解释代码,步骤如下:
- def w1(func): ==>将w1函数加载到内存
- @w1
从表面上看解释器仅仅会解释这两句代码,因为函数在 没有被调用之前其内部代码不会被执行.
但是
@w1内部会执行以下操作:
执行w1函数
执行w1函数 ,并将 @w1 下面的函数作为w1函数的参数,即:@w1 等价于 w1(f1) 所以,内部就会去执行:
def inner():
#验证 1
#验证 2
#验证 3
f1() # func是参数,此时 func 等于 f1
return inner# 返回的 inner,inner代表的是函数,非执行函数 ,其实就是将原来的 f1 函数塞进另外一个函数中
w1的返回值
将执行完的w1函数返回值 赋值 给@w1下面的函数的函数名f1 即将w1的返回值再重新赋值给 f1,即:
新f1 = def inner():
#验证 1
#验证 2
#验证 3
原来f1()
当调用f1()时,就回执行新的f1函数,在新f1函数内部先执行验证,再执行原来的f1函数,然后将原来f1 函数的返回值返回给了业务调用者。
好处:
在不改变原f1代码的情况下,即执行了验证的功能,又执行了原来f1函数的内容,还不改变原来f1函数的调用方式。
万能装饰器函数
demo:
def w1(func):
def inner(*args, **kwargs): # 接收被装饰函数传入参数
print("装饰器内部接收参数:")
print(args)
print(kwargs)
print("内部校验开始...")
return func(*args, **kwargs) # 调用func 需要传入参数
return inner
@w1
def f1(*args, **kwargs): # 传入参数
print("函数传入的参数:")
print(args)
print(kwargs)
print("业务函数开始执行...")
return "嘿嘿嘿,我是返回值"
if __name__ == '__main__':
print("------无参无返回值-------")
f1()
print("------有参无返回值-------")
f1(111)
print("------无参有返回值-------")
print(f1())
print("------有参有返回值-------")
print(f1(222))
概要:
- 内部函数需要有可变参数
- 内部函数调用原函数指向,也要有可变参数
- 被装饰的函数也要有可变参数
- 满足以下四种情况:
- 无参无返回值
- 有参无返回值
- 无参有返回值
- 有参有返回值
多个装饰器装饰同一个函数
demo:
# 短信验证装饰器函数
def msg_veri(func):
print("添加短信验证拓展功能")
def inner1():
print("正在执行短信验证拓展功能...")
return func() # 执行被调用函数的功能
return inner1
# 密码验证装饰器函数
def pwd_veri(func):
print("添加密码验证拓展功能")
def inner2():
print("正在执行密码验证拓展功能...")
return func() # 执行被调用函数的功能
return inner2
@msg_veri
@pwd_veri
def f1():
print("我是被装饰的函数...正在转账")
if __name__ == '__main__':
# 执行转账业务
f1()
调用顺序:
- 添加装饰器功能 从下往上
- 执行装饰器功能 从上往下
理解:
- 代码走到@msg_veri, 无法确定要装饰的函数,往下执行
- 代码走到@pwd_veri,确定要装饰的函数是f1,开始执行pwd_veri(f1),添加密码验证拓展功能,返回inner2给f1,此时f1=inner2
- 此时@msg_veri 确定要装饰的函数是f1,而f1指向inner2,开始执行msg_veri(inner2),添加短信验证拓展功能,返回的是inner1,并且赋值给f1,此时f1=inner1
- 代码走到f1(),调用f1,执行装饰器@msg_veri,执行短信验证拓展功能,然后调用inner2,执行密码验证拓展功能,最后调用原来f1内部的代码(转账功能)
带参数的装饰器装饰函数
需求: 在调用装饰器函数,希望额外传递一个参数 flag
flag == 1 : 开启身份验证拓展功能
flag == 0 : 不开启身份验证拓展功能
demo:
def outer(flag=0): # 装饰器工厂,生产装饰器
def w1(func): # 真正的装饰器函数
def inner():
if flag == 1:
print("正在进行身份校验...")
return func() # 调用原来的func(),将值返回
return inner
return w1
@outer(flag=1)
def f1():
print("主业务函数执行中....")
if __name__ == '__main__':
f1()
理解:
- 先执行@后面的outer(flag=1)函数,返回装饰器w1
- @后变成w1—>@w1,调用真正的装饰器,将f1传入w1中
- 返回inner赋值给f1
- 调用f1时,执行inner函数,先进行校验,再调用原来的主业务函数
类装饰器(几乎不用)
装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重写了 __call__()
方法,那么这个对象就是callable的。
class Test():
def __call__(self):
print('call me!')
t = Test()
t() # call me
类装饰器demo:
class Person(object):
def __init__(self, func):
self.__func = func
def __call__(self, *args, **kwargs):
print("正在进行身份校验...")
return self.__func() # 执行被装饰的函数,并且返回
@Person # 本质是Person(pay) 创建了一个Person的实例对象,并且赋值给了pay
def pay():
print("主业务函数...支付功能")
return "我是返回值"
if __name__ == '__main__':
pay() # pay=Person(pay)实例对象,pay() 实例对象后加()会自动调用类中的__call__方法
理解:
- 当用Person来装作装饰器对pay函数进行装饰的时候,首先会创建Person的实例对象pay=Person(pay),并且会把pay这个函数名当做参数传递到
__init__
方法中。即在__init__
方法中的属性__func指向了pay指向的函数 - pay指向了Person创建出来的实例对象
- 当执行pay()时,相当于实例对象后加(),因此会自动调用类中的
__call__
方法 - 为了能够在
__call__
方法中调用原来pay指向的函数体,所以在__init__
方法中就需要一个实例属性来保存这个函数体的引用
所以才有了self.__func = func
这句代码,从而在调用__call__
方法中能够调用到pay之前的函数体
注意事项
使用函数装饰器会导致函数类型变成warpper类型, 所有的属性及内置函数都失效
def outer(func):
"""
装饰器外部函数 outer
"""
def inner():
"""
装饰器内部函数 inner
"""
return func()
return inner
@outer
def f1():
"""
被装饰的函数f1
"""
pass
if __name__ == '__main__':
print(f1.__name__)
print(f1.__doc__)
结果
inner
装饰器内部函数 inner
解决办法: 对装饰器内部函数加上 @wraps(func) 装饰
from functools import wraps
def outer(func):
"""
装饰器外部函数 outer
"""
@wraps(func)
def inner():
"""
装饰器内部函数 inner
"""
return func()
return inner
@outer
def f1():
"""
被装饰的函数f1
"""
pass
if __name__ == '__main__':
print(f1.__name__)
print(f1.__doc__)
结果
f1
被装饰的函数f1