关于 Python3.6.3 装饰器的一些简单研究

注:作者编程小白,高手勿喷,如有疏漏,还请指正!

正好在廖大大后面的章节里再次看到装饰器,借此机会再次复习了一遍装饰器,发现又有一些新的理解和收获。

为函数定义添加装饰器时,就会执行装饰器代码

譬如下面这段代码,在装饰器中加上一个 print,输出结果发现 func() 并未被执行,但是已经有了 deco(func) 中的 print,

# Code 1

def deco(func):

    print("check where decorator works")

    def wrapper(*args, **kw):
        print('calling %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

@deco
def func():
    print("this is defined in func()")
# Result 1

check where decorator works

在上面的代码下面加入 func(),输出结果中就包含了 func() 内部的代码。

# Result 1 with func() in __main__

check where decorator works
calling func():
this is defined in func()

据此分析:

在为函数定义添加装饰器时,相当于就执行了装饰器的代码,其结果相当于定义了 func(*args, **kw) = wrapper(*args, **kw),即使加上 @functools.wraps(func) ,但其代码路径已经从直接 func() 内部跳到了 wrapper() 里面。也就是说,之后在 main 里面调用 func() 时,本质是在调用 wrapper()。

—— 这也是为什么 main 中执行 func() 时不会再次出现 deco 中的 print!

Wrapper() 中的 func(*args, **kw) 是确保 func() 被实际执行的关键所在!

运行下面一段代码,实际结果发现 func() 定义时的内部代码并没有被执行,而 wrapper() 内部的 print 确实已经输出(在添加装饰器时,wrapper() 内部的 print 是不会输出的,参考 Code 1 的分析),

# Code 2

def deco(func):

    print("check where decorator works")

    def wrapper(*args, **kw):
        print('calling %s():' % func.__name__)
        #return func(*args, **kw) wrapper的返回已被注释掉
    return wrapper

@deco
def func():
    print("this is defined in func()")

func() #这里执行一次 func()
# Result 2

check where decorator works
calling func():

是否一定要 return func(*args, **kw)?

不一定!譬如这段代码,其输出并未报错,但结果不包含 func() 中的 print,并且 print(func()) 的结果是 None!

# Code 3 without returning func() but func() returns 1

def deco(func):

    print("check where decorator works")

    def wrapper(*args, **kw):
        print('calling %s():' % func.__name__)
        #return func(*args, **kw) wrapper的返回已被注释掉
    return wrapper

@deco
def func():
    print("this is defined in func()")
    return 1 #为func()添加返回值

print(func())
# Result 3 without returning func()

check where decorator works
calling func():
None

而 wrapper() 内部 return func() 的结果如下, 这里的 “this is defined in func()” 和 1 都是由于执行 print(func()) 而依次输出的!

# Result 3 with returning func()

check where decorator works
calling func():
this is defined in func()
1

习题1分析

请设计一个decorator,它可作用于任何函数上,并打印该函数的执行时间

贴上代码如下,

# Code 4.1 - Test 1

import time, functools

def metric(fn):    

    @functools.wraps(fn)    
    def wrapper(*args, **kw):
        start_time = time.time()
        x = fn(*args, **kw) #在这里获取fn()的返回值,并通过上下的time.time()获取执行时间
        end_time = time.time()
        print('%s executed in %s ms' % (fn.__name__, 1000 * (end_time - start_time)))
        return x #把fn()的返回值作为wrapper()函数的返回值传递给调用"fn()"的代码
    return wrapper

# 测试
@metric
def fast(x, y):
    time.sleep(0.0012)
    return x + y

@metric
def slow(x, y, z):
    time.sleep(0.1234)
    return x * y * z

f = fast(11, 22)
print(f) #自行添加代码以获取 f 的值
s = slow(11, 22, 33)
print(s) #自行添加代码以获取 s 的值
if f != 33:
    print('测试失败!')
elif s != 7986:
    print('测试失败!')

这里可以看到 wrapper() 内部未必要 return fn(),而可以在 wrapper() 内先通过参数获取 fn() 的返回值,并把该参数的值作为 wrapper() 的返回值传递给调用“fn()”的代码!
——为什么一定要这么做?
——因为我看到评论区有一个答案在 wrapper() 内部是这么写的,

# Code 4.2.1 - Another Wrapper()

def wrapper(*args, **kw):
        start_time = time.time()
        fn(*args, **kw)
        end_time = time.time()
        print('%s executed in %s ms' % (fn.__name__, 1000 * (end_time - start_time)))
        return fn(*args, **kw)
    return wrapper

其运行结果跟上面的 Code 4.1 一致,一开始我认为也是对的,但是后来仔细一想,我认为其中有问题!譬如这样修改 fast() 和 slow() 函数,

# Code 4.2.2 - Another fast() & slow()

const_fast = 1 #定义全局变量 const_fast 查看其值的变化
const_slow = 1 #定义全局变量 const_slow 查看其值的变化

@metric
def fast(x, y):
    global const_fast #声明全局变量 const_fast
    time.sleep(0.0012)
    print("fast:%s" % const_fast) #打印变量 const_fast
    const_fast = const_fast + 1 #[ 重要 ]每当 fast() 被调用一次,全局变量 const_fast 就+1,从而可以通过打印输出查看 fn() 被调用的顺序
    return x + y

# const_slow 同理
@metric
def slow(x, y, z):
    global const_slow
    time.sleep(0.1234)
    print("slow:%s" % const_slow)
    const_slow = const_slow + 1
    return x * y * z

其输出结果如下,可以看到 fast:1 和 fast:2 两次对 fast() 的执行,其中,习题要求的“该函数的执行时间”其实是 fast:1 的运行时间,而 f 的值 33 则是由 fast:2 返回获得,slow:1 和 slow:2 同理。所以,Code 4.2.1 的代码写法其实与习题的要求还是有一些区别的。因此我才写了 Code 4.1 的方法,更加准确!

Result 4.2.2

fast:1
fast executed in 7.000207901000977 ms
fast:2
33
slow:1
slow executed in 126.0073184967041 ms
slow:2
7986

习题2 & 3分析

请编写一个decorator,能在函数调用的前后打印出’begin call’和’end call’的日志。
再思考一下能否写出一个@log的decorator,使它既支持@log()又支持@log(‘execute’)

贴上代码如下,源自一条高手的评论,着实让我佩服!

# Code 5 - Test 2 & 3

import time, functools

def log(arg):
    def decorator(fn):
        @functools.wraps(fn)
        def wrapper(*args, **kwargs):
            print('\r\n%s %s():' % (arg, fn.__name__)) #加入\r\n为了让输出更清晰
            print('begin call') #习题2
            res = fn(*args, **kwargs)
            print('end call') #习题2
            return res #习题2
        return wrapper

    print("check where decorator works")
    if isinstance(arg, str):
        return decorator
    elif hasattr(arg, '__call__'):
        return log('')(arg) #[ 此处是重点! ]
    else:
        pass

@log
def f1():
    print("this is f1()")

@log('execute')
def f2():
    print("this is f2()")

f1()
f2()
# Result 5

check where decorator works
check where decorator works
check where decorator works

 f1(): #等同于 ''(源自 log('')) + ' ' + 'f1()' + ':'
begin call
this is f1()
end call

execute f2():
begin call
this is f2()
end call

其中习题2的“begin call”和“end call”和习题1中的分析完全一致,在此便不再赘述!关键是习题3以及上面代码的对应设计,真是让我心服口服!

首先分析一下3个“check where decorator works”:

@log
def f1():
  1. 这里 f1() = log(f1),f1 是函数名,进入 log(arg) 函数内部,不用看 def decorator(fn) 的代码,直接看到 print(“check where decorator works”) 代码,在此执行,所以首先有第一个输出“check where decorator works”!
  2. 然后 log(f1) 的判断结果为 return log(”)(arg),此处递归用得妙啊!
  3. 于是 f1() = log(f1) = log(”)(f1)!由 log(”) 再次进入 log(arg) 函数内部 于是有了第二个输出“check where decorator works”!
  4. 然后这里 log(”) 的判断结果为 return decorator,所以 f1() = log(f1) = log(”)(f1) = decorator(f1)!
  5. 那么 decorator(f1) 又是什么呢?这时就直接调用 def decorator(fn) 的代码。可以看到 decorator(fn) 的代码内容和最简单的装饰器完全一致,即 f1() = log(f1) = log(”)(f1) = decorator(f1) = wrapper()!

综上,在这段代码执行完时,输出了两个“check where decorator works”,并且定义 f1() = wrapper()!

@log('execute')
def f2():
  1. 参考廖大大的原文可知,这里 f2() = log(‘execute’)(f2),于是其逻辑等同于 f1() 的第3点,并且会输出第三条“check where decorator works”!
  2. 其结果一样就是 f2() = wrapper()!

注意 wrapper() 函数中,print(‘%s %s():’ % (arg, fn._name_) 中的 arg 是 log(arg) 的参数,而非 wrapper(*arg, **kw) 的参数!对于 f1(),这里的 arg = ”;对于 f2(),这里的 arg = ‘execute’。
后面 f1() 和 f2() 的执行输出就不赘述了。

总而言之,核心代码在于“return log(”)(arg)”,妙不可言啊!

猜你喜欢

转载自blog.csdn.net/weixin_36394146/article/details/81318713
今日推荐