如何在Python中编写有效的类装饰器?

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中的装饰器?

有没有解决方法?顺便说一句,我不想​​为这个类中的每个方法添加装饰器.

unu*_*tbu 3

考虑这个循环:

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)创建自己的函数作用域,并使用不同的值namefunc正确绑定。