python进阶5——装饰器

何为装饰器和为什么要用装饰器

python装饰器是对原有函数进行扩展的操作,使用装饰器可以在不改动原有函数的情况下独立扩展现有代码,增加新的功能。
假设有两个函数:

def answer():
    t1 = time.time()
    print('answer the question')
    time.sleep(1)
    t2 = time.time()
    print(t2 - t1)
def quest():
    t1 = time.time()
    print('ask the question')
    time.sleep(1)
    t2 = time.time()
    print(t2 - t1)
answer()
quest()

通过执行,我们可以看见两者的执行时间
在这里插入图片描述
但是如果还要对第三个函数进行一样的运行时间展示,我们还要写一次一样的time,time相减的操作,这相当于重复劳动,有没有办法避免呢?
于是我们想要一个函数,它能以参数的形式,统计其他函数的运行时间:

import time


def func3():
    print('func3')


def count_time(f):
    def wrapper():
        t1 = time.time()
        f()
        print('time cost:', time.time() - t1)

    return wrapper#count_time函数接收f参数,作为参数的函数f在wrapper中执行。
#于是count_time(func)相当于wrapper,
#count_time(func)()相当于wrapper(),wrapper中的f()替换为func()
#于是该函数count_time可以计算所有函数的运行时间,
count_time(func3)()

语法糖

使用上面的函数闭包的方式依然需要调用函数的形式,使用装饰器的语法糖可以更加便捷实现。如果在leecode经常刷题的朋友,应该对

@cache
def dfs(n):

的形式并不陌生,这种做法将已有计算的结果通过cache机制存储起来,加快函数运行到终点的速度。
我们以这样的形式改写我们的装饰器:

import time


def func3():
    print('func3')


def count_time(f):
    def wrapper():
        t1 = time.time()
        f()
        print('time cost:', time.time() - t1)

    return wrapper


@count_time
def func4():
    print('func4')
    time.sleep(1)


func4()

在这里插入图片描述
这里直接调用func4函数,因为有@count_time的装饰,其自动计算了代码的运行时间。

被装饰的函数带参数处理

以上的例子中,函数都是无参函数,有参数时又应该怎么处理呢?改进一下装饰器函数的内部函数就行了:

import time


def func3(n: int):
    for i in range(n):
        print('func3')
    time.sleep(1)


def count_time(f):
    def wrapper(*args, **kwargs):
        t1 = time.time()
        f(*args, **kwargs)
        print('time cost:', time.time() - t1)

    return wrapper


count_time(func3)(4)

在这里插入图片描述

在这里也有常见问题,比如最后一行写成:

count_time(func3(4))()

会报错:
在这里插入图片描述
count_time(func)(4),func为指定的函数,后一个()中为func中需要传入的参数,后者的写法中会先执行func(4),后一次()需要参数而没有参数,自然报错了。

装饰器本身带参数处理

装饰器本身带参数,就将其参数当普通函数参数处理就可。

import time


def func3(n: int):
    for i in range(n):
        print('func3')
    time.sleep(1)


def count_time(f,s:str = 'aaa'):
    def wrapper(*args, **kwargs):
        t1 = time.time()
        f(*args, **kwargs)
        print('time cost:', time.time() - t1)
    print(s)
    return wrapper


count_time(func3,'count_time for func3')(4)

在这里插入图片描述
当然,在使用语法糖形式时有些变化,在装饰器没有参数时,可以自动接受被装饰的函数作为唯一的参数,但是有参数后这样的情况就不再适用了,需要我们自己指定函数的参数。但在这里依然会引出python中函数闭包的问题,使得被修饰的函数带有参数时,问题复杂化。
先看一段错误的代码:

import time


def count_time(s: str = 'aaa'):
    def wrapper(f, *args, **kwargs):
        t1 = time.time()
        f(*args, **kwargs)
        print('time cost:', time.time() - t1)
    print(s)
    return wrapper


@count_time(s='count_time for func')
def func(n: int):
    for i in range(n):
        print('func')
    time.sleep(2)


func(4)

在这里插入图片描述
这里甚至可以正常输出我们给装饰器的参数,但是会提示func()缺失参数。因为第一次传递参数,s确实可以接受到“count_time for func”,但count_time装饰器返回wrapper函数,后者只能接受到func函数作为参数,写在()中的4是无法传递到*args,**kwargs中的,这里即使不使用()调用函数,也是一样的:

import time


def count_time(s: str = 'aaa'):
    def wrapper(f, *args, **kwargs):
        t1 = time.time()
        f(*args, **kwargs)
        print('time cost:', time.time() - t1)
    print(s)
    return wrapper


@count_time(s='count_time for func')
def func(n: int):
    for i in range(n):
        print('func')
    time.sleep(2)


func

在这里插入图片描述
需要调用参数的话,还要加上一次闭包。

import time


def count_time(s: str = 'aaa'):
    def wrapper(f):
        def inner_wrapper(*args, **kwargs):
            t1 = time.time()
            f(*args, **kwargs)
            print('time cost:', time.time() - t1)
        return inner_wrapper
    print(s)
    return wrapper


@count_time(s='count_time for func')
def func(n: int):
    for i in range(n):
        print('func')
    time.sleep(2)


func(4)

这里的func就相当于wrapper(func),其又相当于inner_wrapper,再call传参就相当于inner_wrapper(4)
在这里插入图片描述

类的装饰器

python中,类也可以实现装饰器的,其实现是调用了call魔术方法。

import time

class Time_Counter:
    def __init__(self,func):
        self.func = func
    def __call__(self,*args,**kwargs):
        t1 = time.time()
        self.func(*args,**kwargs)
        print('time cost:',time.time()-t1)

@Time_Counter
def foo():
    print('foo')
    time.sleep(1)


foo()

这里的类装饰器可能也有参数,此时的init 函数就不能传入func了,类似于我们在有参数的装饰器函数的操作,将func作为call的参数传递即可:

import time


class Time_Counter:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            t1 = time.time()
            func(*args, **kwargs)
            print('time cost:', time.time() - t1)
        print(self.x, self.y)
        return wrapper


@Time_Counter('hello', 'world')
def foo(n):
    for i in range(n):
        print('foo')
    time.sleep(1)


foo(4)

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值