装饰器

⼀. 装饰器
在说装饰器之前啊. 我们先说⼀个软件设计的原则: 开闭原则, ⼜被成为开放封闭原则,
你的代码对功能的扩展是开放的, 你的程序对修改源代码是封闭的. 这样的软件设计思路可以
更好的维护和开发.
  开放: 对功能扩展开放
  封闭: 对修改代码封闭

接下来我们来看装饰器. ⾸先我们先模拟⼀下女娲造⼈. 3

def create_people():
 print("⼥娲很厉害. 捏个泥⼈吹⼝⽓就成了⼈了")
create_people()

    ok! 很简单. 但是现在问题来了. 上古时期啊. 天⽓很不稳定. 这时候呢⼤旱三年. 女娲再去
造⼈啊就很困难了. 因为啥呢? 没⽔. 也就是说. 女娲想造⼈必须得先和泥. 浇点⼉⽔才能造

def create_people():
 print("浇⽔") # 添加了个浇⽔功能
 print("⼥娲很厉害. 捏个泥⼈吹⼝⽓就成了⼈了")
create_people()

    搞定. 但是, 我们来想想. 是不是违背了我们最开始的那个约定"开闭原则", 我们是添加了
新的功能. 对添加功能开放. 但是修改了源代码啊. 这个就不好了. 因为开闭原则对修改是封
闭的. 那怎么办. 我们可以这样做.

def create_people():
 # print("浇⽔") # 添加了个浇⽔功能, 不符合开闭原则了
 print("⼥娲很厉害. 捏个泥⼈吹⼝⽓就成了⼈了")
def warter():
 print("先浇⽔")
 create_people() # 造⼈
# create_people() # 这个就不⾏了.
warter() # 访问浇⽔就好了

  现在问题⼜来了. 你这个函数写好了. 但是由于你添加了功能. 重新创建了个函数. 在这之
前访问过这个函数的⼈就必须要修改代码来访问新的函数water() 这也要修改代码. 这个也不
好. 依然违背开闭原则. ⽽且. 如果你这个函数被⼤量的⼈访问过. 你让他们所有⼈都去改. 那
你就要倒霉了. 不⼲死你就⻅⿁了.
  那怎么办才能既不修改原代码, ⼜能添加新功能呢? 这个时候我们就需要⼀个装饰器了. 装
饰器的作⽤就是在不修改原有代码的基础上, 给函数扩展功能

def create_people():
 # print("浇⽔") # 添加了个浇⽔功能, 不符合开闭原则了
 print("⼥娲很厉害. 捏个泥⼈吹⼝⽓就成了⼈了")
def warter(fn):
 def inner():
 print("浇浇⽔")
 fn()
 print("施肥")
 return inner
# # create_people() # 这个就不⾏了.
# warter() # 访问浇⽔就好了
func = warter(create_people)
func() # 有⼈问了. 下游访问的不依然是func么, 不还是要改么?
create_people = warter(create_people)
create_people() # 这回就好了吧,
 

说⼀下执⾏流程: 

 

    1. ⾸先访问warter(create_people).
    2. 把你的⽬标函数传递给warter的形参fn. 那么后⾯如果执⾏了fn意味着执⾏了你的⽬
  标函数create_people
    3. warter()执⾏就⼀句话. 返回inner函数. 这个时候. 程序认为warter()函数执⾏完. 那么
  前⾯的create_people函数名被重新覆盖成inner函数
    4. 执⾏create_people函数. 实际上执⾏的是inner函数. ⽽inner中访问的恰恰使我们最开
  始传递进去的原始的create_people函数

结论: 我们使⽤warter函数把create_people给包装了⼀下. 在不修改create_people的前提下.
完成了对create_people函数的功能添加

 

这是⼀个装饰器的雏形. 接下来我们观察⼀下代码. 很不好理解. 所以呢. 我们可以使⽤语法
糖来简化我们的代

def warter(fn):
 def inner():
 print("浇浇⽔")
 fn()
 print("施肥")
 return inner
@warter # 相当于 create_people = warter(create_people)
def create_people():
 print("⼥娲很厉害. 捏个泥⼈吹⼝⽓就成了⼈了")
create_people()
结果: 
浇浇⽔
⼥娲很厉害. 捏个泥⼈吹⼝⽓就成了⼈了
施肥

    我们发现, 代码运⾏的结果是⼀样的. 所谓的语法糖语法: @装饰器
类似的操作在我们⽣活中还有很多. 比⽅说. 约⼀约.

# 2--> 现在啊, 这个⾏情⽐较不好, 什么⽜⻤蛇神都出来了.
# 那怎么办呢? 问问⾦⽼板. ⾦⽼板在前⾯给⼤家带路
# 这时候我们就需要在约之前啊. 先问问⾦⽼板了. 所以也要给函数添加⼀个功能, 这⾥依然可以使
⽤装饰器
def wen_jin(fn):
 def inner():
 print("问问⾦⽼板, ⾏情怎么样, 质量好不好")
 fn()
 print("⺾, ⾦⽼板骗我")
 return inner
@wen_jin
def yue(): # 1--> 约⼀约函数
 print("约⼀约")
yue()

    ok, 接下来. 我们来看⼀下, 我约的话, 我想约个⼈. 比如约wusir, 这时, 我们要给函数添加
⼀个参数

# 2--> 现在啊, 这个⾏情⽐较不好, 什么⽜⻤蛇神都出来了.
# 那怎么办呢? 问问⾦⽼板. ⾦⽼板在前⾯给⼤家带路
# 这时候我们就需要在约之前啊. 先问问⾦⽼板了. 所以也要给函数添加⼀个功能, 这⾥依然可以使
⽤装饰器
def wen_jin(fn):
 def inner():
 print("问问⾦⽼板, ⾏情怎么样, 质量好不好")
 fn()
 print("⺾, ⾦⽼板骗我")
 return inner
@wen_jin
def yue(name): # 1--> 约⼀约函数
 print("约⼀约", name)
yue("wusir")
结果:
Traceback (most recent call last):
 File "/Users/sylar/PycharmProjects/oldboy/fun_2.py", line 131, in
<module>
 yue("wusir")
TypeError: inner() takes 0 positional arguments but 1 was given

    程序报错. 分析原因: 我们在外⾯访问yue()的时候. 实际上访问的是inner函数. ⽽inner函数
没有参数. 我们给了参数. 这肯定要报错的. 那么该怎么改呢? 给inner加上参数就好了

def wen_jin(fn):
 def inner(name):
 print("问问⾦⽼板, ⾏情怎么样, 质量好不好")
 fn(name)
 print("⺾, ⾦⽼板骗我")
 return inner
@wen_jin
def yue(name):
 print("约⼀约", name)
yue("wusir")

这样就够了么? 如果我的yue()改成两个参数呢? 你是不是还要改inner. 对了. ⽤*args和
**kwargs来搞定多个参数的问题

 

def wen_jin(fn):
def inner(*args, **kwargs): # 接收任意参数
print("问问⾦⽼板, ⾏情怎么样, 质量好不好")
fn(*args, **kwargs) # 把接收到的内容打散再传递给⽬标函数
print("⺾, ⾦⽼板骗我")
return inner
@wen_jin
def yue(name):
print("约⼀约", name)
yue("wusir")

搞定. 这时 wen_jin()函数就是⼀个可以处理带参数的函数的装饰器
光有参数还不够. 返回值呢?

def wen_jin(fn):
 def inner(*args, **kwargs): 
 print("问问⾦⽼板, ⾏情怎么样, 质量好不好")
 ret = fn(*args, **kwargs) # 执⾏⽬标函数. 获取⽬标函数的返回值
 print("⺾, ⾦⽼板骗我")
 return ret # 把返回值返回给调⽤者
 return inner
@wen_jin
def yue(name):
 print("约⼀约", name)
 return "⼩萝莉" # 函数正常返回
r = yue("wusir") # 这⾥接收到的返回值是inner返回的. inner的返回值是⽬标函数的返回
print(r)

返回值和参数我们都搞定了. 接下来给出装饰器的完整模型代码(必须记住)

# 装饰器: 对传递进来的函数进⾏包装. 可以在⽬标函数之前和之后添加任意的功能.
def wrapper(func):
 def inner(*args, **kwargs):
 '''在执⾏⽬标函数之前要执⾏的内容'''
 ret = func(*args, **kwargs)
 '''在执⾏⽬标函数之后要执⾏的内容'''
 return ret
 return inner
# @wrapper 相当于 target_func = wrapper(target_func) 语法糖
@wrapper
def target_func():
 print("我是⽬标函数")
# 调⽤⽬标函数
target_func()

请把以上⾯的代码写10遍, 并理解. 分析每⼀步的作⽤. 

接下来. 我们来看⼀看被装饰器装饰之后的函数名:

# 装饰器: 对传递进来的函数进⾏包装. 可以在⽬标函数之前和之后添加任意的功能.
def wrapper(func):
 def inner(*args, **kwargs):
 '''在执⾏⽬标函数之前要执⾏的内容'''
 ret = func(*args, **kwargs)
 '''在执⾏⽬标函数之后要执⾏的内容'''
 return ret
 return inner
# @wrapper 相当于 target_func = wrapper(target_func) 语法糖
@wrapper
def target_func():
 print("我是⽬标函数")
# 调⽤⽬标函数
target_func()
print(target_func.__name__) # inner
结果:
inner

  我们虽然访问的是target_func函数. 但是实际上执⾏的是inner函数. 这样就会给下游的
程序员带来困惑. 之前不是⼀直执⾏的是target_func么. 为什么突然换成了inner. inner是个
什么⿁?? 为了不让下游程序员有这样的困惑. 我们需要把函数名修改⼀下. 具体修改⽅案:

from functools import wraps # 引⼊函数模块
# 装饰器: 对传递进来的函数进⾏包装. 可以在⽬标函数之前和之后添加任意的功能.
def wrapper(func):
 @wraps(func) # 使⽤函数原来的名字
 def inner(*args, **kwargs):
 '''在执⾏⽬标函数之前要执⾏的内容'''
 ret = func(*args, **kwargs)
 '''在执⾏⽬标函数之后要执⾏的内容'''
 return ret
 return inner
# @wrapper 相当于 target_func = wrapper(target_func) 语法糖
@wrapper
def target_func():
 print("我是⽬标函数")
# 调⽤⽬标函数
target_func()
print(target_func.__name__) # 不再是inner. ⽽是target_func了
@wrapper
def new_target_func():
 print("我是另⼀个⽬标函数")
new_target_func()
print(new_target_func.__name__)

⼆. 装饰器传参
 现在来这样⼀个场景. 还是昨天的约.

from functools import wraps
def wrapper(fn):
 @wraps(fn)
 def inner(*args, **kwargs):
 print("问问⾦⽼板啊, ⾏情怎么样.")
 ret = fn(*args, **kwargs)
 print("⺾, ⾦⽼板骗我")
 return ret
 return inner
@wrapper
def yue():
 print("约⼀次⼜不会死")
yue()

  那么现在如果查的很严. 怎么办呢? 打电话问⾦老板严不严. 那如果整体⻛声都不是那么
紧呢. 是不是就不需要问⾦老板了. 所以. 我们需要⼀个开关来控制是否要询问⾦老板. 这时
我们就需要给装饰器传递⼀个参数. 来通知装饰器要⽤怎么样的⽅式来装饰你的⽬标函数

from functools import wraps
def wrapper_out(flag):
 def wrapper(fn):
 @wraps(fn)
 def inner(*args, **kwargs):
 if flag == True: # 查的严啊. 先问问吧
 print("问问⾦⽼板啊, ⾏情怎么样.")
 ret = fn(*args, **kwargs)
 print("⺾, ⾦⽼板骗我")
 return ret
 else: # 查的不严. 你慌什么
 ret = fn(*args, **kwargs)
 return ret
 return inner
 return wrapper
@wrapper_out(False) # 传递True和False来控制装饰器内部的运⾏效果
def yue():
 print("约⼀次⼜不会死")
 
yue()

  注意: 咱们之前的写法是@wrapper 其中wrapper是⼀个函数. 那么也就是说. 如果我能让
wrapper这⾥换成个函数就⾏了. wrapper(True)返回的结果是wrapper也是⼀个函数啊. 刚刚
好和前⾯的@组合成⼀个@wrapper. 依然还是原来那个装饰器. 只不过这⾥套了3层. 但你要
能看懂. 其实还是原来那个装饰器@wrapper
  执⾏步骤: 先执⾏wrapper(True) 然后再@返回值. 返回值恰好是wrapper. 结果就是
@wrapper

 

三. 多个装饰器装饰同⼀个函数
先读⼀下这样⼀个代码.

def wrapper1(fn):
 def inner(*args, **kwargs):
 print("111")
 ret = fn(*args, **kwargs)
 print("222")
 return ret
 return inner
def wrapper2(fn):
 def inner(*args, **kwargs):
 print("333")
 ret = fn(*args, **kwargs)
 print("444")
 return ret
 return inner
@wrapper2
@wrapper1
def eat():
 print("我想吃⽔果")
eat()
结果: 
333
111
我想吃⽔果
222
444

执⾏顺序: ⾸先@wrapper1装饰起来. 然后获取到⼀个新函数是wrapper1中的inner. 然后执
⾏@wrapper2.这个时候. wrapper2装饰的就是wrapper1中的inner了. 所以. 执⾏顺序就像:
第⼆层装饰器前(第⼀层装饰器前(⽬标)第⼀层装饰器后)第⼆层装饰器后. 程序从左到右执⾏
起来. 就是我们看到的结果

 

posted @ 2019-01-14 16:34  不痴  阅读(142)  评论(0)    收藏  举报