#Python3中装饰器@

本文深入解析Python装饰器的原理及应用,涵盖无参数装饰器、有参数装饰器、@wraps使用方法,以及如何利用装饰器实现函数缓存和日志记录。

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

Python3中装饰器

什么是装饰器?
装饰器,顾名思义,狭义的可以理解为起到一个装饰的作用。
即定义一个函数后,想要重新使这个函数增加新的功能,但是不改变原函数的内容。可以想象着进行一个包装,然后再调用原函数,其实就是调用的包装后的函数。
下面我们通过实例进行阐述:

无参数装饰器

import time 						#def caltime(f):就是一个装饰器,
									#装饰器一般都写在顶上,当函数要调用装饰器的时候
									#用@进行连接 下文中的@caltime
def caltime(f):
    def func():
        t1 = time.time()			#上文导入时间包,t1开始 t2结束 计算f()运算时间
        f()
        t2 = time.time()	
        print(t1)
        print(t2)
        print(t2 - t1)		
    return func							#返回func函数
    
@caltime							#连接上文的装饰器 
def getwater():
    print('取了一杯水')
    print(pow(2,100)/11*30+23**23)
getwater()
#(输出)	取了一杯水
		2.4337696909561263e+31
		1539880939.1847699
		1539880939.1857696
		0.0009996891021728516

@caltime这个语句相当于 执行 getwater = caltime(getwater),为getwater函数装饰并返回func
即getwater=func (实则getwater指向func函数)
所以 getwater()= func()
调用getwater() 实际是调用func()函数。
路径为下:

getwater()-->进入@caltime-->执行def caltime(f)-- 
#(@caltime-->执行def func中间并没有运行func函数,因为并没有进行调用函数func)
--->return func-->func()-->print(t2 - t1)	

有参数的装饰器

import time
def caltime(flag):
    def myfunc(somefunc):
        def func(*args, **kwargs):
            t1 = time.time()
            result = somefunc(*args, **kwargs)
            t2 = time.time()

            if flag:
                print(t2 - t1)
            else:
                print('笑脸', t2 - t1)

            return result

        return func

    return myfunc

@caltime(flag = True)
def sayhello():
    print('hello world')


if __name__ == '__main__':
    sayhello()
    
# 分解:
# 1、calltime(flag = False)
# 必须返回一个函数,并且带有一个参数,该参数是函数 =>myfunc(f)
# 2、sayhello传入到上述返回的函数参数进行传递: 并且返回一个函数 且返回的这个函数是被装饰过的
#    sayhello = myfunc(sayhello)
# 3、调用sayhello,实际调用被装饰过的这个函数
# 4、然后继续往下,sayhello = myfunc(sayhello)=func(myfunc(sayhello))
#实际上调用sayhello(),就为调用func()

#步骤:caltime(flag = True)-->def caltime(flag)-->return myfunc-->@caltime(flag = True)
 #		-->def func(*args, **kwargs)-->return func-->@caltime(flag = True)
# 		-->if __name__ == '__main__':  --> sayhello() ---> t1 = time.time() 到return result
 		
#实际上来说:第一个return 为 myfunc, 接下来执行myfunc(sayhello),相当于sayhello = myfunc,
#			而参数 somefunc = sayhello 然后
#			第二个return 为func,并使用原函数名进行接收,相当于:sayhello = myfunc =  func,
#			最后当调用sayhello()时,执行上述的func(),被装饰过的函数

3.@wraps(fn)装饰,解决不能调出函数的help说明

# 2 装饰器里的@wraps用法
# 我们看这样一个例子:

import  time
def timeit(fn):
    def wrapper(*args, **kwargs):
        start = time.time()
        ret = fn(*args, **kwargs)
        print(time.time() - start)
        return ret
    return wrapper

@timeit
def add(x, y):
    '''x + y'''
    return x + y

help(add)
#(输出)Help on function wrapper in module __main__:
#        wrapper(*args, **kwargs)


# 我们本意是求add的用法,和add的名字,结果给的不是我们想要的,是因为我们调用add的帮助和名
# 字,实际上调用的是wrapper,所以我们返回的都是wrapper的help和name。
# 这里就可以在装饰器timeit里面用@wraps(fn)方法,这个方法在标准库functools里面。这样我们就可以
# 得到想要的结果了,查看帮助是add的帮助,name是add的name:

import  time
from functools import wraps

def timeit(fn):
    @wraps(fn)                  # 下面的函数wrapper就集成了传入的函数的属性。
    def wrapper(*args, **kwargs):
        start = time.time()
        ret = fn(*args, **kwargs)
        print(time.time() - start)
        return ret
    return wrapper

@timeit
def add(x, y):
    '''x + y'''
    return x + y

help(add)
#(输出)Help on function add in module __main__:
#
#       add(x, y)
#           x + y

4.总结详解

# 1 不带参数的装饰器
# 先看一个没有用到装饰器的代码:

import time

def sleep(n):
    time.sleep(n)

start = time.time()
sleep(3)
print(time.time()-start)
#(输出)3.0056068897247314



# 上述代码的作用就是算出sleep(3)这个的执行时间。
# 我们可以改写代码,使得计算函数执行时间的代码作为一个函数,接收一个函数,然后计算这个函数的
# 执行时间。

import time
def timeit(fn, *args, **kwargs):
    start = time.time()
    ret = fn(*args,**kwargs)
    print(time.time()-start)
    return ret
def sleep(n):
    time.sleep(n)
timeit(sleep,3)





# 上面的代码,必须用timeit来传入sleep函数和sleep的参数来运行,我们可不可以提前装饰下要被装饰的
# 函数,然后调用呢?可以的,如下:

import time

def timeit(fn):
    def wrapper(*args, **kwargs):
        start = time.time()
        ret = fn(*args, **kwargs)
        print(time.time()-start)
        return ret
    return wrapper

def sleep(n):
    time.sleep(n)

sleep = timeit(sleep) #提前把sleep函数装饰了。
sleep(3) # 再调用函数的时候,就是调用被装饰过的sleep函数了。
#(输出)3.000748872756958






# Python提供了一种装饰器的语法糖,可以把这三行:

def sleep(n):
    time.sleep(n)
sleep = timeit(sleep)

# 改为下面这三行:

@timeit
def sleep(n):
    time.sleep(n)






# 完整版语法糖版的装饰器示例如下:

import time

def timeit(fn):
    def wrapper(*args, **kwargs):
        start = time.time()
        ret = fn(*args,**kwargs)
        print(time.time()-start)
        return ret
    return wrapper

@timeit
def sleep(n):
    time.sleep(n)

sleep(3)
#(输出)3.004680871963501






# 装饰器的本质是高阶函数,接受一个函数作为参数,并且返回一个函数。
# 装饰器通常会返回一个封装函数(上述代码中的wrapper),这个封装函数在传入的函数前后做一些事
# 情。
# 详解上述代码:
# 我们在timeit装饰器里定义了一个封装函数wrapper,这个wrapper函数在我们真正传入的函数fn的前后
# 做了一些操作,计算出了fn运行的时间差,并且把函数返回回去,然后把这个封装函数wrapper返回回
# 去,替换原来的fn。
# @timeit 然后定义的函数sleep,意味着sleep = timeit(sleep),即sleep即为传入的函数,然后经过
# wrapper的封装,返回回来,替换原来的sleep。

# 2 装饰器里的@wraps用法
# 我们看这样一个例子:

import  time
def timeit(fn):
    def wrapper(*args, **kwargs):
        start = time.time()
        ret = fn(*args, **kwargs)
        print(time.time() - start)
        return ret
    return wrapper

@timeit
def add(x, y):
    '''x + y'''
    return x + y

help(add)
#(输出)Help on function wrapper in module __main__:
#        wrapper(*args, **kwargs)








# 我们本意是求add的用法,和add的名字,结果给的不是我们想要的,是因为我们调用add的帮助和名
# 字,实际上调用的是wrapper,所以我们返回的都是wrapper的help和name。
# 这里就可以在装饰器timeit里面用@wraps(fn)方法,这个方法在标准库functools里面。这样我们就可以
# 得到想要的结果了,查看帮助是add的帮助,name是add的name:

import  time
from functools import wraps

def timeit(fn):
    @wraps(fn)                  # 下面的函数wrapper就集成了传入的函数的属性。
    def wrapper(*args, **kwargs):
        start = time.time()
        ret = fn(*args, **kwargs)
        print(time.time() - start)
        return ret
    return wrapper

@timeit
def add(x, y):
    '''x + y'''
    return x + y

help(add)
#(输出)Help on function add in module __main__:
#
#       add(x, y)
#           x + y








# 3 带参数的装饰器
# 一个程序占用的时间分为用户时间(包括sleep等待的时间),和cpu时间(cpu时间不包括sleep等待的
# 时间)。
# 下面是cpu时间的例子:
import time

def sleep(n):
    time.sleep(3)

start = time.clock()
sleep(3)
print(time.clock() - start)
#(输出)2.9992514567665083

# 我们想写一个装饰器,既可以返回用户时间,又可以返回cpu时间,默认返回的是用户时间(可以用一
# 个开关作为参数,来控制返回的是什么时间),这时候我们就用到了带参数的装饰器:
# 上面是返回用户时间的装饰器。
# 如果后面几行像下面这样写,就是输出cpu时间了:

@timeit(cpu_time = True)        # 传入cpu_time的参数为True,覆盖掉False默认参数
def fun(n):
    time.sleep(n)
    return n

print(fun(3))







# 带参数的装饰器本质是一个函数,传入参数,返回一个新的装饰器。
# 带参数的装饰器只允许一层,如果像这样@xxx()()多层了,就会报错。
import time

def timeit(cpu_time = False):
        # 根据传入的参数,来决定是用time.clock还是time.time
    time_func = time.clock if cpu_time else time.time
    def dec(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            start = time_func()
            ret = fn(*args, **kwargs)
            print(time_func() - start)
            return ret
        return wrapper
    return dec

# 下面注释,等同于@timeit()那段
# def fun(n):
# time.sleep(n)
# return n
# fun = timeit()(fun)
# 其实上面这行,用柯里化就非常好理解了。
@timeit() # 参数为空,但也是带了参数了,空参数,传入的参数为默认参数,等同于@timeit(cpu_time
def fun(n):
    time.sleep(n)
    return n

fun(3)






# 多个装饰器装饰的时候,采用就近原则,然后一层层装饰,比如:
#@a
#@b
#@c
#def fn():
    pass
# 上述等效于a(b(c(fn)))




# 4 装饰器的应用
#     给函数调用做缓存
from functools import wraps
def memo(fn):
    cache = {}
    miss = object()
    
    @wraps(fn)
    def wrapper(*args):
        result = cache.get(args, miss)
        if result is miss:
        result = fn(*args)
        cache[args] = result
        return result
    return wrapper

@memo
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

# 上面这个例子中,是一个斐波拉契数例的递归算法。我们知道,这个递归是相当没有效率的,因为会重
# 复调用。比如:我们要计算fib(5),于是其分解成fib(4) + fib(3),而fib(4)分解成fib(3)+fib(2),fib(3)又分
# 解成fib(2)+fib(1)…… 你可看到,基本上来说,fib(3), fib(2), fib(1)在整个递归过程中被调用了两次。
# 而我们用decorator,在调用函数前查询一下缓存,如果没有才调用了,有了就从缓存中返回值。一下
# 子,这个递归从二叉树式的递归成了线性的递归。







# 4.2 给函数打日志
# 下面这个示例演示了一个logger的decorator,这个decorator输出了函数名,参数,返回值,和运行时
# 间。

 
from functools import wraps
def logger(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        ts = time.time()
        result = fn(*args, **kwargs)
        te = time.time()
        print("function = {0}".format(fn.__name__))
        print(" arguments = {0} {1}".format(args, kwargs))
        print(" return = {0}".format(result))
        print(" time = %.6f sec" % (te-ts))
        return result
    return wrapper
    
@logger
def multipy(x, y):
    return x * y
@logger
def sum_num(n):
    s = 0
    for i in range(n+1):
        s += i
    return s
print(multipy(2, 10))
print(sum_num(100))
print(sum_num(10000000))
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值