关于Python的装饰器(2)

Python中被装饰器修饰的函数,解析后会生成一个参数是被修饰函数的装饰器函数对象,可以调用,可以接受传参(如果被修饰的函数定义了参数),实际调用的时候,尽管代码里值写了被修饰的函数,被调用的却是最终生成的复杂对象。对于这个对象,目前有以下几点疑问未得到确认:

1、最后生成的对象在被调用时,参数传递的顺序是什么样的?

2、装饰器函数中如果和被修饰函数中有同名变量,会产生什么结果?

3、如果装饰器函数返回对象是一个与获得的参数对象无关的可调用对象,会对运行结果产生什么影响?

 

1、运行以下代码:

 1 def addSpan(fn):
 2     print "addSpan executed"
 3     def n(*args):
 4         print "spam, spam, spam"
 5         print "args of func n:", str(args)
 6         return fn(*args)
 7     return n
 8 
 9 @addSpan
10 def useful(a, b):
11     print "args of useful:", (a, b)
12     print a**2 + b**2
13 
14 def synonym(a, b):
15     print a**2 + b**2
16 
17 
18 if __name__ == '__main__':
19     useful(3, 4)

运行结果是:

addSpan executed
spam, spam, spam
args of func n: (3, 4)
args of useful: (3, 4)
25
[Finished in 0.1s]

 没有异步逻辑,所有函数都是顺序执行的。通过结果可以看出,在运行被修饰的函数时,装饰器函数内部的函数n被首先执行,而它的参数就是useful被调用时传入的参数,然后就是按照n被声明时的顺序,逐行执行。fn最后执行,也就是被修饰的函数。

•对于第一行的装饰器函数的输出,现在还有些疑问,将在后面验证。

根据以上运行结果可以得出结论:装饰器的作用是构造一个可以访问到被修饰函数的函数对象(闭包?),这个函数对象接收被修饰函数被调用时的参数作为运行参数,而执行的逻辑则是另外的逻辑,甚至可以与被修饰函数完全无关。在上述代码中,即使注释掉第6行的return fn(*args),程序仍旧可以正常运行,运行结果如下:

addSpan executed
spam, spam, spam
args of func n: (3, 4)
[Finished in 0.1s]

可以确认,装饰器与被修饰的函数逻辑可以完全无关。

运行时参数会直接传给装饰器函数返回的可调用对象,被修饰函数是否会被执行视装饰器的内部函数逻辑而定,不存在必须执行被修饰函数的限制。

 

2、运行以下代码:

 1 def addSpan(fn):
 2     print "addSpan executed"
 3     t = "Hello " * 3 + "blablabla"
 4     def n(*args):
 5         print "spam, spam, spam"
 6         print "args of func n:", str(args)
 7         print "t of n:", t
 8         return fn(*args)
 9     return n
10 
11 @addSpan
12 def useful(a, b):
13     print "args of useful:", (a, b)
14     print a**2 + b**2
15     t = a * b
16     print 't in useful:', t
17 
18 if __name__ == '__main__':
19     useful(3, 4)

运行结果:

addSpan executed
spam, spam, spam
args of func n: (3, 4)
t of n: Hello Hello Hello blablabla
args of useful: (3, 4)
25
t in useful: 12
[Finished in 0.1s]

可以看出,装饰器函数中的变量与被修饰函数中的同名变量互不干扰。

经过验证,分别注释掉第3行的t声明或者第15行的t声明,都一样会在各自的函数中报出找不到变量的错误。由此可知,装饰器函数的变量域和被修饰函数的变量域不存在继承关系。

 

更进一步,如果装饰器函数和被修饰函数使用同一个全局变量,会产生什么结果?

运行如下代码:

 1 def addSpan(fn):
 2     global T
 3     T = "Hello " * 3 + "blablabla"
 4     print 'id of global variable T:', id(T)
 5 
 6     print "addSpan executed"
 7     def n(*args):
 8         T = str(args)
 9         print "id of T in n:", id(T)
10         print "spam, spam, spam"
11         print "args of func n:", str(args)
12         print "T in n:", T
13         return fn(*args)
14     return n
15 
16 @addSpan
17 def useful(a, b):
18     print "args of useful:", (a, b)
19     print a**2 + b**2
20     T = a * b
21     print 'T in useful:', T
22     print "id of T in useful:", id(T)
23 
24 
25 @addSpan
26 def otherFunc():
27     print "otherFunc executed"
28     print "T in otherFunc:", T
29     print "id of T in otherFunc:", id(T)
30 
31 if __name__ == '__main__':
32     useful(3, 4)
33     print ''
34     otherFunc()

运行结果如下:

id of global variable T: 4513798704
addSpan executed
id of global variable T: 4513798896
addSpan executed
id of T in n: 4513827168
spam, spam, spam
args of func n: (3, 4)
T in n: (3, 4)
args of useful: (3, 4)
25
T in useful: 12
id of T in useful: 140312483777024

id of T in n: 4513769432
spam, spam, spam
args of func n: ()
T in n: ()
otherFunc executed
T in otherFunc: Hello Hello Hello blablabla
id of T in otherFunc: 4513798896
[Finished in 0.1s]

通过观察不同函数里变量T的id,可以发现关于全局变量的猜想似乎没有太大意义,每次T被重新赋值时,会重新生成变量T,只有在T没有被声明过的时候,才会寻找全局变量。

 

3、如果装饰器函数的返回对象是与被修饰函数无关的对象,会有什么结果?

上文1中已经解释了一部分可能产生的结果:只要语法正确,即使不执行业务逻辑,程序也不会出错。

换个思路,如果装饰器函数有返回对象,但是执行另外的逻辑,会发生什么?

运行以下代码:

 1 def addSpan(fn):
 2     print "addSpan executed"
 3     def n(*args):
 4         print "spam, spam, spam"
 5         print "args of func n:", str(args)
 6         return sorted(args)  // 不调用参数fn
 7     return n
 8 
 9 @addSpan
10 def useful(a, b):
11     print "args of useful:", (a, b)
12     print a**2 + b**2
13 
14 if __name__ == '__main__':
15     useful(3, 4)

运行结果:

addSpan executed
spam, spam, spam
args of func n: (3, 4)
[Finished in 0.1s]

正常结束,除了没有运行被修饰函数本身的逻辑。

换个写法:

def otherFunc(*args):
    print "otherFunc executed"
    print "args in otherFunc:", str(args)

def addSpan(fn):
    print "addSpan executed"
    return otherFunc // 运行另外函数

@addSpan
def useful(a, b):
    print "args of useful:", (a, b)
    print a**2 + b**2

if __name__ == '__main__':
    useful(3, 4)

运行结果:

addSpan executed
otherFunc executed
args in otherFunc: (3, 4)
[Finished in 0.1s]

一切正常,被修饰的函数的作用类似另一个函数otherFunc的别名。

经过以上验证,可以确定,装饰器函数如果执行与被修饰函数无关的函数,程序运行不会出错,只是被修饰函数的逻辑不会被执行,此时被修饰函数的仅是作为装饰器函数的程序运行入口而存在。

 

• 另外,据官方文档,在解析被装饰器修饰的函数时,每个被修饰的函数在经过编译之后,内容将保持被解析时的状态不会被改变,直到程序被重新编译。这一点上面2的结论也可以解释:变量域固定且互相隔离。

以下代码关于变量t的变化也可以说明:

 1 from time import time
 2 
 3 def addSpan(fn):
 4     print "addSpan executed"
 5     t = time()
 6     def n(*args):
 7         print "now is %f" % t
 8         print "spam, spam, spam"
 9         print "args of func n:", str(args)
10         return fn(*args)
11     return n
12 
13 @addSpan
14 def useful(a, b):
15     print "args of useful:", (a, b)
16     print a**2 + b**2
17 
18 
19 @addSpan
20 def otherFunc():
21     print "otherFunc executed"
22 
23 
24 if __name__ == '__main__':
25     useful(3, 4)
26     print ''
27     useful(4, 6)
28     print ''
29     otherFunc()

运行结果:

addSpan executed
addSpan executed
now is 1470139094.499294
spam, spam, spam
args of func n: (3, 4)
args of useful: (3, 4)
25

now is 1470139094.499294
spam, spam, spam
args of func n: (4, 6)
args of useful: (4, 6)
52

now is 1470139094.499297
spam, spam, spam
args of func n: ()
otherFunc executed
[Finished in 0.1s]

两次调用useful的 t 的值都相同,又与调用otherFunc时 t 的值不同。

 

• 在上面的运行结果中也可以发现,即使被修饰的函数没有执行,在运行时,装饰器函数的print记录依然出现了,是否可以理解为编译时,装饰器函数已经被执行,被存放在内存中等待被调用的,只是装饰器函数返回的可调用对象。

posted @ 2016-08-02 20:00 harelion 阅读(...) 评论(...) 编辑 收藏