大师兄的Python学习笔记(十三): 理解装饰器

大师兄的Python学习笔记(十二): 常用高级函数
大师兄的Python学习笔记(十四): 迭代器、生成器和协程

一、关于装饰器

  • 是Python中最难理解的概念之一,也是最"Pythonic"的用法之一。
  • 可以理解是在为函数附加功能,让代码更简洁。
  • 需要先理解闭包等概念。
  • 本文仅能帮助理解装饰器的概念,更深入的用法还需不断学习。

二、理解装饰器的逻辑

1. 在函数中创建函数
  • 先举个简单的例子,如果在函数中创建函数并调用:
>>>def outer(): 
>>>    print("joining inner ... ...")  # 第二步
>>>    def inner():
>>>        print("here in inner!") # 第四步
>>>    inner() # 第三步
>>>    print("left inner ... ...") # 第五步
 
>>>if __name__ == "__main__":
>>>    outer() # 第一步
joining inner ... ...
here in inner!
left inner ... ...
2. 将函数作为参数并调用
  • Python中一切皆对象,所以函数当然也可以当成对象使用。
  • 用函数作为参数的方式实现案例1。
>>>def outer(func):  # 第二步
>>>    print("joining inner ... ...") # 第三步
>>>    func() # 第四步
>>>    print("left inner ... ...") # 第六步 

>>>def inner():
    print("here in inner!") # 第五步    

>>>if __name__ == "__main__":
>>>    outer(inner)  # 第一步
joining inner ... ...
here in inner!
left inner ... ...
3. 用老版装饰器的方式实现
  • 这是老版本装饰器的实现方式,将函数本身作为参数传递给装饰器,并用装饰器为自己赋值。
>>>def outer(func):
>>>    def wrapper():
>>>        print("joining inner ... ...") #第三步
>>>        func() #第四步
>>>        print("left inner ... ...") #第六步
>>>    return wrapper() #第二步

>>>if __name__ == "__main__":
>>>    def inner():
>>>        print("here in inner!") #第五步
>>>    inner = outer(inner) #第一步
joining inner ... ...
here in inner!
left inner ... ...
4. 用添加了语法糖@的装饰器实现
  • 在新版本中,为了简化代码,用语法糖@替代了 = ()的部分。
>>>def outer(func):
>>>    def wrapper():
>>>        print("joining inner ... ...") # 第三步
>>>        func() # 第四步
>>>        print("left inner ... ...") # 第六步
>>>    return wrapper() # 第二步

>>>if __name__ == "__main__":
>>>    @outer 
>>>    def inner(): # 第一步
>>>        print("here in inner!") # 第五步
joining inner ... ...
here in inner!
left inner ... ...
5. 解读案例3
  • 装饰器用@的语法糖表示,放在被装饰函数的上一行。
  • 在上面的案例中,@outer是函数inner()的装饰器
  • 根据程序运行的顺序可以看出,在使用装饰器的方式时,被装饰的函数作为参数传入装饰器中。
  • 需要理解装饰器和案例1、2的关系。
6. 再换个角度理解装饰器
  • 假如我们已经创建了“飞翔”的装饰器函数,并将其看成是一种功能。
  • 可以创建任何函数(“人”或“鱼”),通过装饰器为他们赋予“飞翔”的功能,见下方代码:
>>>def fly(func):
>>>    def wrapper():
>>>        func()
>>>        print("I can fly now!\n")
>>>    return wrapper()

>>>if __name__ == "__main__":
>>>    @fly
>>>    def human():
>>>        print("I am human!")
    
>>>    @fly
>>>    def fish():
>>>        print("I am fish!")
I am human!
I can fly now!

I am fish!
I can fly now!

三、装饰器的传参

1. 闭包的简单概念
  • 在函数A中嵌套另一个函数B,如果函数B引用了函数A的变量,则产生闭包,这些参数可以保存在函数B的作用域中。
>>>def A(param):
>>>    def B():
>>>        res = param + 1
>>>        return res
>>>    return B
>>>res = A(1)
>>>print(res())
2
  • 另一个例子更好理解闭包。
>>>def A(month):
>>>    def B(date):
>>>        calendar= month + "/" + date
>>>        return calendar
>>>    return B
>>>match_month = A("10月")
>>>print(match_month("5日"))
10月/5日
>>>print(match_month("20日"))
10月/20日
2. 先理解不用语法糖的装饰器传参
>>>def outer(func,para):
>>>    def decorator(func):
>>>        def wrapper(func):
>>>            print("joining {} ... ...".format(para)) #第四步 
>>>            func(para) #第五步
>>>            print("left {} ... ...".format(para))#第七步
>>>        return wrapper(func)#第三步
>>>    return decorator(func) #第二步

>>>if __name__ == "__main__":
>>>    def inner(para):
>>>        print("here in {}!".format(para)) #第六步
>>>    inner = outer(inner,"inner") #第一步
joining inner ... ...
here in inner!
left inner ... ...
  • 思路是将函数和参数分别存在闭包中,所以需要多一层函数。
  • 在装饰器中将函数和参数一起调用。
3. 用语法糖的装饰器传参
>>>def outer(para):
>>>    def decorator(func):
>>>        def wrapper(func):
>>>            print("joining {} ... ...".format(para)) #第四步 
>>>            func(para) #第五步
>>>            print("left {} ... ...".format(para)) #第七步
>>>        return wrapper(func)#第三步
>>>    return decorator #第二步

>>>if __name__ == "__main__":
>>>    @outer("inner") #第一步
>>>    def inner(para):
>>>        print("here in {}!".format(para))  #第六步
joining inner ... ...
here in inner!
left inner ... ...
  • 装饰器语法糖会将被装饰函数作为一个参数传回。
  • 其余部分与2.5相同。

四、装饰器的更多用法

1. 多个装饰器修饰同一个函数
  • 可以用多个装饰器修饰一个函数,方法是在函数上方添加装饰器,每行一个。
  • 本质上就是多个函数嵌套。
  • 顺序是先完成离函数最近的装饰器。
>>>import time

>>>def count_time(func):
>>>    def wrapper(*args,**kargs):
>>>        t0 = time.perf_counter()  # 第二步 
>>>        func(*args,**kargs)
>>>        t1 = time.perf_counter() # 第七步 
>>>        print("耗时:{}秒".format(int(t0 - t1)))  # 第八步 
>>>    return wrapper # 第二步 

>>>def notice(func):
>>>    def wrapper(*args,**kargs):
>>>        print("开始等待...")  # 第四步  
>>>        func(*args,**kargs)
>>>        print("结束等待...") # 第六步 
>>>    return wrapper 

>>>if __name__ == '__main__':
>>>    @count_time
>>>    @notice
>>>    def wait():
>>>        time.sleep(5) # 第三步(引用函数名) /# 第五步 (执行)
>>>    wait() # 第一步 
开始等待...
结束等待...
耗时:-5秒
2. 无参数的类装饰器
  • 需要使用类的构造函数__init__(初始化时触发)和魔法函数__call__(类被当做函数调用时触发)。
>>>class outer():
>>>    def  __init__(self,func):
>>>        self.func = func
>>>    def __call__(self,*args,**kargs):
>>>        print("joining inner...")
>>>        self.func()
>>>        print("left inner...")

>>>if __name__ == '__main__':
>>>    @outer
>>>    def inner():
>>>        print('here in inner!')
>>>    inner()
joining inner...
here in inner!
left inner...
3. 带参数的类装饰器
  • 与3.3相同,需要在__call__中增加一层函数,用于获取参数。
  • 需要仔细看代码理解思路。
>>>class outer():
>>>    def  __init__(self,para):
>>>        self.para = para
>>>    def __call__(self,func):
>>>        def wrapper(*args,**kargs):
>>>            print('here in {}...'.format(self.para))
>>>            func(*args,**kargs)
>>>            print('here in {}...'.format(self.para))
>>>        return wrapper

>>>if __name__ == '__main__':
>>>    @outer(para = "outer")
>>>    def inner(para):
>>>        print('here in {}!'.format(para))
>>>    inner("inner")
here in outer...
here in inner!
here in outer...
4. 类的装饰器
  • 只要类可以被调用,就可以被装饰器装饰。
>>>def outer(cls):
>>>    def wrapper():
>>>        print('here in outer...')
>>>        cls()
>>>        print('here in outer...')
>>>    return wrapper

>>>if __name__ == '__main__':
>>>    @outer
>>>    class inner():
>>>        def __init__(self):
>>>            print('here in inner')
>>>    inner()
here in outer...
here in inner
here in outer...

参考资料


  • https://blog.csdn.net/u010138758/article/details/80152151 J-Ombudsman
  • https://www.cnblogs.com/zhuluqing/p/8832205.html moisiet
  • https://www.runoob.com 菜鸟教程
  • http://www.tulingxueyuan.com/ 北京图灵学院
  • http://www.imooc.com/article/19184?block_id=tuijian_wz#child_5_1 两点水
  • https://blog.csdn.net/weixin_44213550/article/details/91346411 python老菜鸟
  • https://realpython.com/python-string-formatting/ Dan Bader
  • https://www.liaoxuefeng.com/ 廖雪峰
  • https://blog.csdn.net/Gnewocean/article/details/85319590 新海说
  • https://www.cnblogs.com/Nicholas0707/p/9021672.html Nicholas
  • 《Python学习手册》Mark Lutz
  • 《Python编程 从入门到实践》Eric Matthes

本文作者:大师兄(superkmi)

你可能感兴趣的:(大师兄的Python学习笔记(十三): 理解装饰器)