类方法中的装饰者:与' getattr'的兼容性

时间:2016-09-12 16:09:31

标签: python oop metaprogramming decorator python-decorators

我需要为类方法创建包装器,以便在调用特定方法之前和/或之后执行。

这是一个最小的例子:

class MyClass:

    def call(self, name):
        print "Executing function:", name
        getattr(self, name)()

    def my_decorator(some_function):
        def wrapper():
            print("Before we call the function.")
            some_function()
            print("After we call the function.")
            return wrapper

    @my_decorator
    def my_function(self):
        print "My function is called here."


engine = MyClass()
engine.call('my_function')

这会在getattr(self, name)()行显示错误:

  

TypeError:' NoneType'对象不可调用

如果我在类方法之前注释掉装饰器,它可以完美地运行:

class MyClass:

    def call(self, name):
        print "Executing function:", name
        getattr(self, name)()

    def my_decorator(some_function):
        def wrapper():
            print("Before we call the function.")
            some_function()
            print("After we call the function.")
            return wrapper

    # @my_decorator
    def my_function(self):
        print "My function is called here."


engine = MyClass()
engine.call('my_function')

输出结果为:

  

执行功能:my_function

     

我的功能在这里被调用。

装饰器本身与教科书示例相同。当使用getattr在Python中调用装饰方法时,看起来在低级别出现问题。

您对如何修复此代码有任何想法吗?

2 个答案:

答案 0 :(得分:2)

这与getattr()无关。当您尝试直接调用my_function()时,会收到完全相同的错误:

>>> engine.my_function()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable

你有两个问题:

  • 您的装饰者永远不会返回wrapper,因此会返回None。此返回值将替换my_function,并且是导致错误的直接原因; MyClass.my_function设置为None

    >>> MyClass.my_function is None
    True
    
  • 您的包装器不带任何参数,包括self。一旦你正确地退回它,你就需要它才能工作。

通过取消缩进return wrapper行来解决第一个问题;它目前是wrapper函数本身的一部分,应该成为my_decorator的一部分:

def my_decorator(some_function):
    def wrapper(self):
        print("Before we call the function.")
        # some_function is no longer bound, so pass in `self` explicitly
        some_function(self)
        print("After we call the function.")
    # return the replacement function
    return wrapper

答案 1 :(得分:1)

您的问题只是部分回答。以下是如何修改wrapper(以及call())方法,以便它们接受其他参数 - 这将使其完全有效(以及Python 2和3):

class MyClass:

    def call(self, name, *args, **kwargs):
        print("Executing function: {!r}".format(name))
        getattr(self, name)(*args, **kwargs)

    def my_decorator(some_function):
        def wrapper(self, *args, **kwargs):
            print("Before we call the function.")
            retval = some_function(self, *args, **kwargs)
            print("After we call the function.")
            return retval
        return wrapper

    @my_decorator
    def my_function(self):
        print("My function is called here.")

    del my_decorator  # Not a permanent part of class.


engine = MyClass()
engine.call('my_function')

输出:

Executing function: 'my_function'
Before we call the function.
My function is called here.
After we call the function.