Bob*_*ang 6 python closures scope class decorator
我刚刚编写了一个类装饰器,如下所示,试图为目标类中的每个方法添加调试支持:
import unittest
import inspect
def Debug(targetCls):
for name, func in inspect.getmembers(targetCls, inspect.ismethod):
def wrapper(*args, **kwargs):
print ("Start debug support for %s.%s()" % (targetCls.__name__, name));
result = func(*args, **kwargs)
return result
setattr(targetCls, name, wrapper)
return targetCls
@Debug
class MyTestClass:
def TestMethod1(self):
print 'TestMethod1'
def TestMethod2(self):
print 'TestMethod2'
class Test(unittest.TestCase):
def testName(self):
for name, func in inspect.getmembers(MyTestClass, inspect.ismethod):
print name, func
print '~~~~~~~~~~~~~~~~~~~~~~~~~~'
testCls = MyTestClass()
testCls.TestMethod1()
testCls.TestMethod2()
if __name__ == "__main__":
#import sys;sys.argv = ['', 'Test.testName']
unittest.main()
Run Code Online (Sandbox Code Playgroud)
运行上面的代码,结果是:
Finding files... done.
Importing test modules ... done.
TestMethod1 <unbound method MyTestClass.wrapper>
TestMethod2 <unbound method MyTestClass.wrapper>
~~~~~~~~~~~~~~~~~~~~~~~~~~
Start debug support for MyTestClass.TestMethod2()
TestMethod2
Start debug support for MyTestClass.TestMethod2()
TestMethod2
----------------------------------------------------------------------
Ran 1 test in 0.004s
OK
Run Code Online (Sandbox Code Playgroud)
您可以找到两次打印'TestMethod2'.
有问题吗?我的理解是否适合python中的装饰器?
有没有解决方法?顺便说一句,我不想为这个类中的每个方法添加装饰器.
考虑这个循环:
for name, func in inspect.getmembers(targetCls, inspect.ismethod):
def wrapper(*args, **kwargs):
print ("Start debug support for %s.%s()" % (targetCls.__name__, name))
Run Code Online (Sandbox Code Playgroud)
当wrapper最终被调用时,它会查找 的值name。在 locals() 中没有找到它,它会在for-loop. 但那时 已经for-loop结束,并name引用循环中的最后一个值,即TestMethod2。
因此,两次调用包装器时,name计算结果均为TestMethod2。
解决方案是创建一个name绑定到正确值的扩展范围。这可以通过带有默认参数值的函数 来完成closure。默认参数值在定义时评估和固定,并绑定到同名的变量。
def Debug(targetCls):
for name, func in inspect.getmembers(targetCls, inspect.ismethod):
def closure(name=name,func=func):
def wrapper(*args, **kwargs):
print ("Start debug support for %s.%s()" % (targetCls.__name__, name))
result = func(*args, **kwargs)
return result
return wrapper
setattr(targetCls, name, closure())
return targetCls
Run Code Online (Sandbox Code Playgroud)
在评论中 eryksun 提出了一个更好的解决方案:
def Debug(targetCls):
def closure(name,func):
def wrapper(*args, **kwargs):
print ("Start debug support for %s.%s()" % (targetCls.__name__, name));
result = func(*args, **kwargs)
return result
return wrapper
for name, func in inspect.getmembers(targetCls, inspect.ismethod):
setattr(targetCls, name, closure(name,func))
return targetCls
Run Code Online (Sandbox Code Playgroud)
现在closure只需要解析一次。每次调用都会closure(name,func)创建自己的函数作用域,并使用不同的值name和func正确绑定。