python 装饰器原理教程

装饰器原理

统计1—100奇数,并计算用时。
import time

def print_odds():
    start_time = time.time()
    for i in range(100):
        if 1 % 2 == 1:
            print(i)
    end_time = time.time()
    print("it takes {} s to find all the olds".format(end_time-start_time))


if __name__ == '__main__':
    print_odds()

观察发现,以上代码主要由函数逻辑(查找奇数)和辅助功能(记录时间),但是两个功能耦合在一起了。

  • 缺点:使修改起来不方便,容易引起bug
  • 能不能将复制函数从主要功能函数中抽取出来?
import time

def print_odds():
    for i in range(100):
        if 1 % 2 == 1:
            print(i)

def count_time(func):
    start_time = time.time()
    func()
    end_time = time.time()
    print("it takes {} s to find all the olds".format(end_time-start_time))

if __name__ == '__main__':
    count_time(print_odds)

将辅助函数(记录时间)抽离成函数count_time,在辅助函数count_time中调用主要功能函数print_odds,

  • 优点:解耦没函数功能分离
  • 缺点:要通过辅助函数来调用主要函数功能,不方便。
  • 目标:能不能在调用主函数时自动完成对时间的统计?
import time
def print_odds():
    for i in range(100):
        if 1 % 2 == 1:
            print(i)

# 闭包本质上是一个函数
# 闭包函数的传入参数和返回值都是函数
# 闭包函数的返回值函数是对传入函数进行增强后的结果
def count_time_wrapper(func):
    def improved_func():
        start_time = time.time()
        func()
        end_time = time.time()
        print("it takes {} s to find all the olds".format(end_time - start_time))
    return improved_func

if __name__ == '__main__':
    print_odds = count_time_wrapper(print_odds)
    print_odds()

以上代码可以通过辅助函数来调用主要函数功能,但是调用时还是比较麻烦。

import time

def count_time_wrapper(func):
    def improved_func():
        start_time = time.time()
        func()
        end_time = time.time()
        print("it takes {} s to find all the olds".format(end_time - start_time))
    return improved_func


@count_time_wrapper
def print_odds():
    for i in range(100):
        if 1 % 2 == 1:
            print(i)

            
if __name__ == '__main__':
    print_odds()

通过装饰器进行函数增强,只是一种语法糖,本质上遇上一个完全一致。

闭包传参,返回值

问题:对于含有返回值的函数,调用闭包增强函数后,不能成功返回,但成功的增强了辅助函数。
对于含有参数的函数,调用闭包增强后,不能成功接收参数。

案例如下:

import time


def count_time_wrapper(func):
    def improved_func():
        start_time = time.time()
        func()
        end_time = time.time()
        print("it takes {} s to find all the olds".format(end_time - start_time))
    return improved_func


def count_odds(lim=100):
    cnt = 0
    for i in range(lim):
        if i % 2 == 1:
            cnt += 1
    return cnt


if __name__ == '__main__':
    print('增强前,不传参')
    count_odds()
    print('--------------')
    print('增强后,手动调用闭包,不传参')
    count_odds = count_time_wrapper(count_odds)
    print(count_odds())
    print('---------------------------')
    print('增强前,传参')
    count_odds(lim=90)
    print('--------------')
    print('增强后,手动调用闭包,传参')
    count_odds = count_time_wrapper(count_odds)
    print(count_odds(lim=90))

运行结果

增强前,不传参
it takes 1.0967254638671875e-05 s to find all the olds
--------------
增强后,手动调用闭包,不传参
it takes 8.106231689453125e-06 s to find all the olds
it takes 1.5974044799804688e-05 s to find all the olds
None
---------------------------
增强前,传参
Traceback (most recent call last):
  File "/Users/lidong/Desktop/zhouyu/project/crawl-lidong/crawlab_env/lib/python3.9/site-packages/IPython/core/interactiveshell.py", line 3441, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-4-68245ad7e616>", line 9, in <module>
    count_odds(lim=90)
TypeError: improved_func() got an unexpected keyword argument 'lim'

解决方案: 引入变量,将原函数返回结果返回给增强函数,解决增强函数返回值为None问题。
添加参数,将参数传给原函数,使原函数可以接收到参数。

案例如下(手动调用闭包):

import time


def count_time_wrapper(func):
    def improved_func(*args, **kwargs):# 添加参数,将参数传给原函数,使原函数可以接收到参数
        start_time = time.time()
        ret = func(*args, **kwargs) #引入变量,将原函数返回结果返回给增强函数,解决增强函数返回值为None问题
        end_time = time.time()
        print("it takes {} s to find all the olds".format(end_time - start_time))
        return ret
    return improved_func


def count_odds(lim=100):
    cnt = 0
    for i in range(lim):
        if i % 2 == 1:
            cnt += 1
    return cnt


if __name__ == '__main__':
    print('增强前,不传参')
    count_odds()
    print('--------------')
    print('增强后,手动调用闭包,不传参')
    count_odds = count_time_wrapper(count_odds)
    print(count_odds())
    print('---------------------------')
    print('增强前,传参')
    count_odds(lim=90)
    print('--------------')
    print('增强后,手动调用闭包,传参')
    count_odds = count_time_wrapper(count_odds)
    print(count_odds(lim=90))

运行结果:

增强前,不传参
--------------
增强后,手动调用闭包,不传参
it takes 5.9604644775390625e-06 s to find all the olds
50
---------------------------
增强前,传参
it takes 4.76837158203125e-06 s to find all the olds
--------------
增强后,手动调用闭包,传参
it takes 3.814697265625e-06 s to find all the olds
it takes 6.9141387939453125e-06 s to find all the olds

案例如下(自动调用闭包):

import time


def count_time_wrapper(func):
    def improved_func(*args, **kwargs):# 添加参数,将参数传给原函数,使原函数可以接收到参数
        start_time = time.time()
        ret = func(*args, **kwargs) #引入变量,将原函数返回结果返回给增强函数,解决增强函数返回值为None问题
        end_time = time.time()
        print("it takes {} s to find all the olds".format(end_time - start_time))
        return ret
    return improved_func

@count_time_wrapper
def count_odds(lim=100):
    cnt = 0
    for i in range(lim):
        if i % 2 == 1:
            cnt += 1
    return cnt


if __name__ == '__main__':
    print('增强前,不传参')
    count_odds()
    print('--------------')
    print('增强后,自动调用闭包,不传参')
    print(count_odds())
    print('---------------------------')
    print('增强前,传参')
    count_odds(lim=90)
    print('--------------')
    print('增强后,自动调用闭包,传参')
    print(count_odds(lim=90))

运行结果

增强前,不传参
it takes 1.3113021850585938e-05 s to find all the olds
--------------
增强后,自动调用闭包,不传参
it takes 1.0013580322265625e-05 s to find all the olds
50
---------------------------
增强前,传参
it takes 7.867813110351562e-06 s to find all the olds
--------------
增强后,自动调用闭包,传参
it takes 7.152557373046875e-06 s to find all the olds
45

装饰器面试题

  1. 装饰器在第一次调用被装饰函数时进行增强。
  2. 当有多个装饰器时,怎么执行。
  • 手动执行

def wrapper1(func1):
    print('set fun1')
    def improved_func1():
        print('call func1')
        func1()
    return improved_func1

def wrapper2(func2):
    print('set fun2')
    def improved_func2():
        print('call func2')
        func2()
    return improved_func2


# @wrapper1
# @wrapper2
def original_func():
    print('主函数')

if __name__ == '__main__':
    print(original_func.__name__)   # original_func = original_func
    original_func = wrapper2(original_func)
    print(original_func.__name__)  # original_func = improved_func2
    original_func = wrapper1(original_func)
    print(original_func.__name__)  # original_func = improved_func1
    original_func()

运行结果

original_func
set fun2
improved_func2
set fun1
improved_func1
call func1
call func2
主函数
  • 装饰器
def wrapper1(func1):
    print('set fun1')
    def improved_func1():
        print('call func1')
        func1()
    return improved_func1

def wrapper2(func2):
    print('set fun2')
    def improved_func2():
        print('call func2')
        func2()
    return improved_func2


@wrapper1
@wrapper2
def original_func():
    print('主函数')

if __name__ == '__main__':
    original_func()

运行结果

set fun2
set fun1
call func1
call func2
主函数
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值