Python修饰器【由浅入深->实例】

前言

Python的修饰器,我认为是一种类似于神器的存在,能够化腐朽为神奇。有以下几个功能

  1. 能够显著减少代码量
  2. 使编写的程序条理清晰
  3. 易于快速组织代码

这里,我不想用一些标准化的概念来约束,我将用一个案例,逐步思考我们需要什么,逐渐的引入修饰器,从实用的角度来分析,会有更加深刻的体会。


一、我们来算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执行器,检测到某个对象被修饰器修饰后,会直接调用修饰器,同时把这个对象传入修饰器

可以把对象传入修饰器,而具体这个**对象要干什么,或者不干什么,均由我们自己决定**!!这个就是修饰器的妙用
整个实战已经讲完了,具体其中使用的本质也说明了,后面会给出两个例子,抛砖引玉,供大家触类旁通。(尤其线程池的使用大家可以仔细阅读源码)


三、我们不想写的啰嗦–扩展实例

Python日志组件–基于修饰器

Python线程池–基于修饰器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值