Discuss / Python / 注意装饰器的运行时机

注意装饰器的运行时机

Topic source

儒生脱尘

#1 Created at ... [Delete] [Delete and Lock User]

我写的这个包含了运行的具体时间和运行的时机:

from functools import wraps
from time import time, sleep

start_time = time()
print("\n现在开始运行...\n\n**********************\n")

def log(text):
    def decorator(func):
        @wraps(func)
        def wrapers(*args, **kw):
            print("函数{0}()即将执行,此时系统已运行了 {1} 秒\n".format(func.__name__, time()-start_time))
            startTime =  time()
            return (func(*args, **kw),print("函数{0}()执行了 {1} 秒后,结束了自己\n".format(func.__name__, time()-startTime)))[0]
        return wrapers
    return (decorator,print("我是一个带参数的装饰器,我的参数是 '{}' ".format(text) ))[0] if text.__str__() == text else decorator(text)

@log
def abc():
    print("我是函数abc(),我正在执行中,不过我要睡 5 秒\n")
    sleep(5)

@log('嘿,伙计,是你吗?\n')
def efg():
    print("我是函数efg(),我正在执行中,我只想睡 3 秒\n")
    sleep(3)    

abc()
efg()

print("运行结束,一共运行了",time()-start_time,"秒")

运行结果:


现在开始运行...

**********************

我是一个带参数的装饰器,我的参数是 '嘿,伙计,是你吗?'

函数abc()即将执行,此时系统已运行了 0.002000093460083008 秒

我是函数abc(),我正在执行中,不过我要睡 5 秒

函数abc()执行了 5.000285863876343 秒后,结束了自己

函数efg()即将执行,此时系统已运行了 5.004286050796509 秒

我是函数efg(),我正在执行中,我只想睡 3 秒

函数efg()执行了 3.001171588897705 秒后,结束了自己

运行结束,一共运行了 8.005457639694214 秒

实际上可以看出,在文件运行时,函数还未执行前,装饰器自己先运行了一阵。

  • 给装饰器传递参数时,返回了 decorator 这个函数自动执行,然后又返回了warpers,所以在运行函数 efg() 之前,efg函数已变成了wrapers函数。
  • 没有参数时,返回 wrapers函数,所以在运行函数 abc() 之前,abc函数已变成了wrapers函数
  • 所以,你以为你执行了abc(),或者efg()这个函数,其实不然,你只是执行了wrapers()这个函数
  • 总而言之,装饰器的功能就是一个 在保持此函数功能不变的基础上,给它额外增加了一些功能

有点理解了!

你好,看了你的回复之后收益匪浅,但是还是有个疑惑,如果我在abc()这个函数里不是print而是return,那怎么在后面打出end-call呢?我尝试了很多方法都是先执行end-call才执行函数中的计算,最后都是begin-end-函数返回值这个结果,而不是begin-函数返回值-end

儒生脱尘

#4 Created at ... [Delete] [Delete and Lock User]

@看我头像啊哈哈 你只要先运行函数,再返回返回值就好了啊:

第一种方法:

...
result = func(*args, **kw):
print('End Call--')
return result
...

第二种方法:

...
return [func(*args, **kw), print('End Call--')][0]
...

上面两种方法是等效的,都是先执行func(*args, kw),然后打印 End Call, 然后再把func(args, *kw)的返回值返回出去

个人推荐第二种方法,比较符合python的精神:Pythonic

但是

def log(fun):
    def wrapper(*args,**kw):
        print("start")
        return [fun(*args,**kw),print("end")][0]
    return wrapper
@log
def test(x):
    return x

print(test("a"))

运行的结果是: start end a 不能打出start a end

GodBian

#6 Created at ... [Delete] [Delete and Lock User]

@看我头像啊哈哈 你要搞清楚当你执行test("a")的时候,这个test函数已经不是你定义的test函数,而是wrapper函数。所以,你想要在这个函数结束后再输出end,你必须再用一个装饰器去装饰·wrapper。 你现在的函数是 1.先执行print("start") 2.执行你的test(x),然而这里什么都没做,只return了一个x 3.执行完test(x)后,执行print('end') 4.整个函数,连同装饰器一起返回一个x = "a",并且执行print("a")

儒生脱尘

#7 Created at ... [Delete] [Delete and Lock User]

@看我头像啊哈哈

你对执行的时机理解的还是有些误解:

def log(fun):
    def wrapper(*args,**kw):
        print("start")
        return [fun(*args,**kw),print("end")][0]
    return wrapper
@log
def test(x):
    return x

print(test("a"))

装饰器log作为test函数的装饰器,使得函数运行前后进行一些额外工作外,最终返回了函数的执行结果

@log
def test(x):
    return x

添加了@log,简而言之,可视为最终使test函数变成了这样:

def test(x):
    print("start")
    def test_raw(arg):
        return arg
    return [test_raw(x), print('end')][0]

所以你执行 print(test("a")),结果肯定是:

start
end
a

因为你这个 print 是包裹在函数外面的。所以运行时机肯定是在函数运行完之后。

信勒个达

#8 Created at ... [Delete] [Delete and Lock User]

请问为什么先执行的abc(),但是log中会先出现(我是一个带参数的装饰器,我的参数是 '嘿,伙计,是你吗?')这个结果?

你那个print在最后面打印 跟里面的东西都没关系了 兄弟

儒生脱尘

#10 Created at ... [Delete] [Delete and Lock User]

@信勒个达

因为在函数执行之前,装饰器先运行了,并且最终返回了wrapers这个函数。 @log 这个语法糖,是直接把函数装进去,然后再返回装饰之后的函数。这个时机是不需要函数执行的。 所以参数打印出来的时机在执行函数前。


  • 1
  • 2

Reply