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

标签 python class scope closures 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()

运行上面的代码,结果是:

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

你会发现 '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))

wrapper 最终被调用时,它会查找 name 的值。在 locals() 中找不到它,它会在 for-loop 的扩展范围内查找(并找到它)。但那时 for-loop 已经结束,name 指的是循环中的最后一个值,即 TestMethod2

因此两次调用包装器时,name 的计算结果为 TestMethod2

解决方案是创建一个扩展范围,其中 name 绑定(bind)到正确的值。这可以通过具有默认参数值的函数 closure 来完成。默认参数值在定义时被评估并固定,并绑定(bind)到同名变量。

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

在评论中,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

现在 closure 只需解析一次。每次调用 closure(name,func) 都会创建自己的函数作用域,并正确绑定(bind) namefunc 的不同值。

关于python - 如何在 Python 中编写有效的类装饰器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8091948/

相关文章:

python - 如何在 PySpark 1.6 中将 DataFrame 列从字符串转换为 float / double ?

android - 如何创建一个类的实例?

java - 方法本地内部类成员范围访问

python - 检查列表的最后一个元素是否与选择的字符串匹配

python - numpy 将一维数组堆叠成结构化数组

python - 命令 python yowsup-cli -c config ... 不适用于树莓派

c++ - 如何访问从模板类继承的私有(private)静态类成员?

java - 创建和访问 Vector 类

javascript - 为什么 JavaScript 在函数实际调用之前声明局部变量?

ruby-on-rails - 使用常量还是类变量?