目录
前言
Python的修饰器,我认为是一种类似于神器的存在,能够化腐朽为神奇。有以下几个功能
- 能够显著减少代码量
- 使编写的程序条理清晰
- 易于快速组织代码
这里,我不想用一些标准化的概念来约束,我将用一个案例,逐步思考我们需要什么,逐渐的引入修饰器,从实用的角度来分析,会有更加深刻的体会。
一、我们来算1+1的问题–日志打印
1. 第一阶段
这里我写了一个函数计算两数之和
def my_sum(a, b):
return a+b
OK,我要用这个函数计算1+1,同时看到输入输出日志消息,实现如下:
if __name__ == '__main__':
a = 1
b = 1
result = my_sum(a, b)
print(a, b, "->", result)
# 输出值
1 1 -> 2
2. 第二阶段
现在,我需要计算1+1,1+2 … 到1+5共五次,如果每次都在调用后打印日志,这样会造成函数调用前后一堆打印日志的无关的代码出现;此时我们可以把打印日志放入函数中,比如这样
def my_sum(a, b):
result = a+b
print(a, b, "->", result)
return result
这样,执行时就只调用了函数,并没有写打印日志的代码
if __name__ == '__main__':
my_sum(1, 1)
my_sum(1, 2)
my_sum(1, 3)
my_sum(1, 4)
my_sum(1, 5)
# 输出值
1 1 -> 2
1 2 -> 3
1 3 -> 4
1 4 -> 5
1 5 -> 6
3. 第三阶段
接着,现在我写一个计算两数之积、两数之差等等一共10个函数,依旧需要同时看到输入输出日志消息,如果我们在每一个实现函数中都打印日志,代码重复率高,一点也不优雅。我们通过观察可以看到,其实每次都是计算完才需要打印,我们能不能写一个函数,每次计算完成后直接调用呢?当然可以:
def my_sum(a, b):
result = a + b
my_print(a, b, result=result)
return result
def my_print(*args, result):
print(args, "->", result)
if __name__ == '__main__':
my_sum(1, 1)
my_sum(1, 2)
my_sum(1, 3)
my_sum(1, 4)
my_sum(1, 5)
# 输出值
(1, 1) -> 2
(1, 2) -> 3
(1, 3) -> 4
(1, 4) -> 5
(1, 5) -> 6
4. 第四阶段
这时候,我们顺利的完成任务,但是每次都写my_print(a, b, result=result)也很麻烦,这一行代码我也不想写,我能不能让它每次计算完毕,自动的调用打印函数呢?没问题,这时我们的修饰器就可以上场了
名称 | 作用 |
---|---|
修饰器 | 修饰函数或者类 |
使用方式 | @XXXX 其中XXX为我们定义修饰器函数的名称 |
作用 | 当Python执行器,检测到某个对象被修饰器修饰后,会直接调用修饰器,同时把这个对象传入修饰器 |
根据描述,实现如下
def my_print(func):
@functools.wraps(func)
def inner(*args, **kwargs):
result = func(*args, **kwargs)
print(args, "->", result)
return result
return inner
@my_print
def my_sum(a, b):
return a + b
if __name__ == '__main__':
my_sum(1, 1)
my_sum(1, 2)
my_sum(1, 3)
my_sum(1, 4)
my_sum(1, 5)
# 输出值
(1, 1) -> 2
(1, 2) -> 3
(1, 3) -> 4
(1, 4) -> 5
(1, 5) -> 6
首先我们定义了一个函数
my_print(func)
这个函数入参只有一个,func:函数,函数本身也可以作为参数传入。
@functools.wraps(func)
上面代码是保证修饰器不会对被修饰的函数造成影响
在内部的一个函数中,调用传入的函数参数func,同时对输入*args, **kwargs(*args代表位置参数,**kwargs代表关键字参数)和结果进行打印。
@my_print
def my_sum(a, b):
return a + b
然后我们在求和函数上@my_print。其效果如前面所说,每当我们调用my_sum()函数的时候,就可以直接打印输入输出,而不必每一个函数都写打印日志的代码。看起来更加优雅,让我们专注于函数逻辑的实现。
5. 最终阶段
接下来我又要作妖了,在打印的日志里面,我想把输出值的 -> 变为我像要的字符,并且,我想根据我的心情,有的函数用-> 有的用>>。这里能实现吗?依旧没问题,使用带参数的修饰器即可:
def my_print_plus(mark):
def middle(func):
@functools.wraps(func)
def inner(*args, **kwargs):
result = func(*args, **kwargs)
print(args, mark, result)
return result
return inner
return middle
和上面一样,定义修饰器函数,这里注意观察,这里有三层函数嵌套,最外层负责传递修饰器的参数进来,里面的两层和不带参数的修饰器函数一样:
@my_print_plus("->")
def my_sum(a, b):
return a + b
@my_print_plus(">>")
def my_max(a, b):
if a > b:
return a
else:
return b
if __name__ == '__main__':
my_sum(1, 1)
my_max(2, 3)
# 输出值
(1, 1) -> 2
(2, 3) >> 3
OK,根据打印日志的思路,我们已经把修饰器的功能都给讲完了。纵观整个过程,我们根据想法一点点的构建,从基础打印,到后面使用修饰器优雅的打印日志,逐步进阶,最后完成了一个优雅的日志输出。
二、我们可以想干什么就干什么–思路扩展
1. 白嫖一个函数
def my_print_plus(mark):
def middle(func):
@functools.wraps(func)
def inner(*args, **kwargs):
result = func(*args, **kwargs)
print(args, mark, result)
return result
return inner
return middle
这里我们仔细的观察修饰器函数,其实被修饰的函数执行是在这里:
result = func(*args, **kwargs)
如果不写这一行代码呢?
有童鞋会想到了,我们可以借用函数去执行我们想要的逻辑,而不是函数本身的执行,比如修饰器函数这样写
def my_print_null(mark):
def middle(func):
@functools.wraps(func)
def inner(*args, **kwargs):
print(args, mark, "这是一个空的")
return inner
return middle
我们调用
@my_print_null(">>")
def my_sum(a, b):
return a + b
if __name__ == '__main__':
my_sum(2, 3)
# 输出值
(2, 3) >> 这是一个空的
我们可以看到,my_sum函数并没有执行,仅仅通过修饰器把它的入参给打印出来了
2. 对函数“偷参换数”
其实我们也可以把通过修饰器把参数传给被修饰的函数:
def my_print_plus(mark, *ref):
def middle(func):
@functools.wraps(func)
def inner(*args, **kwargs):
result = func(*ref)
print(ref, mark, result)
return result
return inner
return middle
@my_print_plus(">>", 1, 1)
def my_sum(a, b):
return a + b
if __name__ == '__main__':
my_sum(2, 3)
# 输出结果
(1, 1) >> 2
我们看到,我们执行的是2+3,可是日志的结果显示的我们计算的是1+1,当然,结果是正确的=2
3. 扩展总结
其实我们前面提到了:
Python执行器,检测到某个对象被修饰器修饰后,会直接调用修饰器,同时把这个对象传入修饰器
可以把对象传入修饰器,而具体这个**对象要干什么,或者不干什么,均由我们自己决定**!!这个就是修饰器的妙用
整个实战已经讲完了,具体其中使用的本质也说明了,后面会给出两个例子,抛砖引玉,供大家触类旁通。(尤其线程池的使用大家可以仔细阅读源码)