Python装饰器介绍(一)
一. 预备知识
1.1 Python函数中的可变参数
- 函数参数可以分为形参(必填参数, 默认参数, 可变参数)和实参(位置参数, 关键字参数)
- 可变参数能够支持函数接收任意数量的参数(实参)
- *args表示收集所有多余的位置参数到一个args元组中, 其中args是arguments的缩写,
- **kwags表示收集所有多余的关键字参数到一个kwargs字典中, 其中kwargs是keyword arguments的缩写
# -*- coding: utf-8 -*- def test1(*args, **kwargs): print(f"这是额外的位置参数: {args}") print(f"这是额外的关键字参数: {kwargs}") t = ('a', 'b') d = {'c': 'c', 'd': 'd'} # *表示对元素解包; **表示对字典解包 test1(*t, **d) # 等价于: test1("a", "b", c="c", d="d")
1.2 对闭包函数的理解
- python中的函数有一些特点: 函数内可以定义函数; 函数可以作为参数被传递; 函数可以作为另一个函数的返回值
- 闭包函数的本质其实是一个嵌套函数
- 外层函数为内层函数提供了一个运行环境, 内层函数被当成返回值返回给外层函数
- 从python语法上, 可以通过函数的__closure__方法来判断一个函数是否为闭包函数
# -*- coding: utf-8 -*- def test1(): def inner1(m, n): return m + n return inner1 def test2(m): def inner2(n): return m + n return inner2 # [test1函数]不是闭包函数 print(test1.__closure__) # 运行结果: None # [test1函数]内的[inner1函数]是闭包函数 inner1 = test1() print(inner1.__closure__) # 运行结果: None # [test2函数]不是闭包函数 print(test2.__closure__) # 运行结果: None # [test2函数]内的[inner2函数]是闭包函数 inner2 = test2(5) print(inner2.__closure__) # 运行结果: (<cell at 0x02FED430: int object at 0x7989D470>,)
二. 装饰器的介绍
2.1 装饰器是python中一类比较有特色的语法, 用来装饰函数和类
2.2 装饰器在不破坏原有函数结构的情况下, 可以进行功能扩展
2.2 使用装饰器, 可以增加代码的可读性, 让代码层次结构变得更加清晰
三. 装饰器的实现
3.1 编写一个最简单的装饰器
实现代码:
# -*- coding: utf-8 -*- # 被装饰函数 def myfunc(): return "hello chinablue!" # 装饰器: 在函数执行前 def mylog(func): def inner(): print(f"这是执行函数前打印的信息") return func() return inner print(mylog(myfunc)())
执行结果:
这是执行函数前打印的信息
hello chinablue!
3.2 编写一个通用装饰器
实现功能:
同一个装饰器可以装饰参数个数不同的函数
实现代码:
# -*- coding: utf-8 -*- # 被修饰函数1 def myfunc1(a, b): return a + b # 被修饰函数2 def myfunc2(a, b, c): return a + b + c # 装饰器 def mylog(func):
# 运用可变参数后, 可以装饰任何带参数的函数 def inner(*args, **kwargs): res = func(*args, **kwargs) print(f"执行结果为: {res}") return res return inner # myfunc1函数: 2个必填参数 mylog(myfunc1)(1, 2) # myfunc2函数: 3个必填参数 mylog(myfunc2)(1, 2, 3)
执行结果:
执行结果为: 3
执行结果为: 6
3.3 使用@语法糖方式调用装饰器
实现代码:
# -*- coding: utf-8 -*- # 装饰器 def mylog(func): def inner(*args, **kwargs): res = func(*args, **kwargs) print(f"执行结果为: {res}") return res return inner @mylog def add(a, b): return a + b # 没有改变函数的调用方式 add(1, 2)
执行结果:
执行结果为: 3
3.4 装饰器会篡改原函数的属性
因为使用装饰器, 原函数(即被装饰函数)的地址指向了装饰器所定义的内部inner函数
# 打印add函数的属性 print(f"{add.__name__}") # 执行结果: inner print(f"{add.__doc__}") # 执行结果: None
改进代码:
# -*- coding: utf-8 -*- from functools import wraps # 装饰器 def mylog(func): # 可以借助functools.warps函数, 避免属性篡改问题 @wraps(func) def inner(*args, **kwargs): res = func(*args, **kwargs) print(f"执行结果为: {res}") return res return inner @mylog def add(a, b): """ 功能: 实现加法运算 :param a: :param b: :return: """ return a + b # 打印函数的属性 print(f"add函数的名字: {add.__name__}") print(f"add函数的注释信息: {add.__doc__}") add(1, 2)
执行结果:
add函数的名字: add add函数的注释信息: 功能: 实现加法运算 :param a: :param b: :return: 执行结果为: 3
3.5 带参数的装饰器
实现功能:
实现一个可以设置日志等级的log装饰器
思路分析:
为了让装饰器能够传参, 需要再嵌套一层函数
实现代码:
# -*- coding: utf-8 -*- from functools import wraps # 装饰器 def mylog(level="INFO"): def outter(func): @wraps(func) def inner(*args, **kwargs): res = func(*args, **kwargs) print(f"[{level}]函数{func.__name__}的执行结果为: {res}") return res return inner return outter @mylog(level="INFO") def add(a, b): return a + b @mylog(level="ERROR") def sub(a, b): return a - b # 打印函数的属性 add(1, 2) sub(1, 2)
执行结果:
[INFO]函数add的执行结果为: 3
[ERROR]函数sub的执行结果为: -1
3.6 一个函数可以同时使用多个装饰器
# -*- coding: utf-8 -*- from functools import wraps # 装饰器1 def mylog1(func): @wraps(func) def inner(*args, **kwargs): res = func(*args, **kwargs) print(f"执行结果为: {res}") return res return inner # 装饰器2 def mylog2(level="INFO"): def outter(func): @wraps(func) def inner(*args, **kwargs): res = func(*args, **kwargs) print(f"[{level}]函数{func.__name__}的执行结果为: {res}") return res return inner return outter @mylog2(level="DEBUG") @mylog1 def add(a, b): return a + b # 多个装饰器的执行顺序: 从近到远依次执行 add(1, 2)
使用函数调用方式调用装饰器
mylog2(level="ERROR")(mylog1(add))(1, 2)
四. 本文小结
1. 装饰器可以在不改变原函数代码,不改变原函数调用方式的情况下, 给函数添加扩展功能
2. 装饰器的本质就是一个嵌套函数, 如果装饰器需要接收参数, 则需要增加一层函数嵌套
3. 装饰器有两种调用方式: 1函数传参方式, 2使用@语法糖
4. 装饰器的外层函数接收的是被装饰函数, 返回的是内层函数
5. 装饰器的内层函数(即闭包函数)负责装饰被装饰函数
6. 装饰器的常用场景有: 打印日志, 性能测试, 权限验证等