python装饰器的简单理解

本文深入探讨Python装饰器的应用,讲解其如何简化代码并避免重复,通过实例展示装饰器在函数扩展中的作用,如计算执行时间、参数判断及求参数之和等。同时,文章介绍装饰器的嵌套使用及参数传递方式,包括函数定义时和执行时的参数传递。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

python装饰器有着非常重要且广泛的应用。在python语言中,引入装饰器的目的主要是简化代码,避免代码块的重复。装饰器的引入可以对函数进行扩展,使函数具有额外的功能,如事务处理、性能测试、缓存等等。这里我们举「计算函数执行时间」这个例子。

计算某个函数的执行时间,可以使用如下代码:

def func1():
    start = time()
    sleep(1)	#do something
    end = time()
    print("total execute time of {} is {}".format(func1.__name__, end - start))

func1()
#Out:total execute time of func1 is 1.002723217010498

可见,让一个函数实现「计算自身执行时间」的功能大致需要三行代码,可能感觉三行还是比较短的,直接放在函数体内部就好了。但是,如果现在有三个函数需要执行这个功能呢?那么代码如下:

def func1():
    start = time()
    sleep(1)
    end = time()
    print("total execute time of {} is {}".format(func1.__name__, end - start))

def func2():
    start = time()
    sleep(1)
    end = time()
    print("total execute time of {} is {}".format(func1.__name__, end - start))
    
def func3():
    start = time()
    sleep(1)
    end = time()
    print("total execute time of {} is {}".format(func1.__name__, end - start))

func1()
func2()
func3()

上述代码已经出现了很多重复冗余的部分,三个函数「计算执行时间」的代码都是一样的。设想一下,如果我们有1000个函数需要计算时间,那么计算时间部分的代码有3000行,这还是比较恐怖的。所以,我们需要考虑如何能够减少代码量的冗余。

此时就是装饰器大显身手的时候了,装饰器可以对一个函数进行功能上的扩展。装饰器本身也是一个函数,传给他的型参是要被扩展的函数对象,同时也返回一个函数对象。对一个函数用装饰器扩展时,只需要在这个函数定义的时候,使用关键字(语法糖)@声明其装饰器名称。

将函数的「计算执行时间」功能改造成利用装饰器的形式,代码如下:

def timer(func):	#这里传进来的func就是被扩展的函数
    def wrapper():
        start = time()
        func()	#函数执行的地方,即执行sleep(1)
        end = time()
        print("total execute time of {} is {}".format(func.__name__, end - start))
    return wrapper

@timer
def func1():
    sleep(1)

@timer
def func2():
    sleep(1)

@timer
def func3():
    sleep(1)

func1()
func2()
func3()

根据上述代码可见,原本3行的函数时间计算,变为仅仅1行的@timer,这使得代码变得简洁且代码的可重复利用性变强。假如现在有n个需要调用这个功能的函数,代码量就可以减少2(n-1)行。

值得注意的是,在python中一切事物皆对象,其中函数也是对象。在装饰器的第一行,函数func作为一个函数对象传入装饰器中,这个func后面没有括号,所以可以被传递但不执行;如果函数名后面带有小括号,则该函数会执行。

上述例子都是不需要任何参数的,但有如果不进行任何的参数传递,会导致装饰器总是执行固定的逻辑,仅仅局限在某些单一的场景。我们引入装饰器的目的在于减少代码量,所以当两个逻辑非常相近时,如果用不传参的装饰器,就可能需要写两个装饰器来实现两个相近的逻辑。然而,当装饰器可以传进参数时,完全就可以利用一个装饰器来实现两个或多个相近的逻辑。

例如现在每个函数的休眠时间不再是固定的1s,而是一个变量;现需要对这个sleep的时间做一个判断,判断这个函数是「巨能睡」或是「巨不能睡」。

有两种传递参数的方式:其一是在函数定义的时候传递参数,此时参数的传递需要在语法糖@那一行进行;第二种是在函数执行的时候传递参数,此时参数传递在调用原函数的地方进行。下面是第一种参数传递的代码:

def judger(t):	#传入的t是函数定义时传递的参数,语法糖@那一行的
    def wrapper(func):
        if t > 5:
            print("巨能睡")
        else:
            print("巨不能睡")
        func()

    return wrapper

@judger(3)
def func1():
    sleep(3)


@judger(6)
def func2():
    sleep(6)
#Out:
#巨不能睡
#巨能睡

在上述代码中,我们对函数扩展的功能是判定这个函数「能不能睡」,此时就需要「睡眠时间」这个参数传入装饰器。与之前无参数的不一样之处,在于装饰器定义的名称judger后面需要接收语法糖@那一行传进来的参数。在函数定义的时候就讲3和6传入到装饰器中。

第二种是在函数执行时传递参数的方法,为了消除参数个数的限定,一般使用动态参数*args,和**kwargs。其中*args是一个参数的元组(tuple),**kwargs是一个记录关键字及其对应值的字典。其示例代码如下:

def adder(func):	#这里传进来的func就是被扩展的函数
    def wrapper(*args, **kwargs):
        total = 0
        for item in args:
            total += item
        print(total)
    return wrapper

@adder
def func1(*args, **kwargs):
    pass

func1(3, 5, 7)
#Out:15

这里我定义的装饰器拓展的功能是,求取函数在执行中的所有参数之和。由于这里没有在函数定义时传参,所以在装饰器第一行没有必要接收@那行传递的参数。

由此可以发现,写一个装饰器最多需要三重嵌套。这种情况对应的是参数传递既有定义函数时传递的参数,也有函数执行时传递的参数。这个三重嵌套也相对简单,只需要在上面第二种情况的基础上,在最外面再嵌套一层,用于接收语法糖@那行传递的参数。这种装饰器大致格式如下:

def judger(*args, **kwargs):  #函数定义时传来的参数,即3
    def wrapper(func):
        print(args)
        def dec(*args, **kwargs):	#函数执行时传递的参数,即2,4,5
            """do something"""
            func(*args, **kwargs)
            print(args)

        return dec
    return wrapper

@judger(3)
def func1(*args, **kwargs):
    pass

func1(2,4,5)
#Out:
(3,)
(2, 4, 5)

这种两个地方都有参数传递的形式算是最复杂的了,也只需要写三个嵌套即可。

最后还有一个小trick,对于上述代码,一般还需要调用一个模块以保留原函数的元信息:

from functools import wraps

def judger(*args, **kwargs):
	@wraps(func)
    def wrapper(func):
        print(args)
        wraps(func)
        def dec(*args, **kwargs):
            """do something"""
            func(*args, **kwargs)
            print(args)

        return dec
    return wrapper

@judger(3)
def func1(*args, **kwargs):
    pass

func1(2,4,5)

如果不使用这个模块,那么func1会失去他的元信息,如__name__,__doc__等等,导致这些信息都变成了装饰器函数judger中的对应信息。因此,为了让这些属性与我们的直觉更加匹配,最好加上这个模块。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值