【问题标题】:Python - can I programmatically decorate class methods from a class instance?Python - 我可以以编程方式从类实例中装饰类方法吗?
【发布时间】:2012-12-15 06:14:09
【问题描述】:

我有一个对象层次结构,其中几乎所有的方法都是类方法。如下所示:

class ParentObject(object):
    def __init__(self):
        pass

    @classmethod
    def smile_warmly(cls, the_method):
        def wrapper(kls, *args, **kwargs):
            print "-smile_warmly - "+kls.__name__
            the_method(*args, **kwargs)
        return wrapper

    @classmethod
    def greetings(cls):
        print "greetings"

class SonObject(ParentObject):
    @classmethod
    def hello_son(cls):
        print "hello son"

    @classmethod
    def goodbye(cls):
        print "goodbye son"

class DaughterObject(ParentObject):
    @classmethod
    def hello_daughter(cls):
        print "hello daughter"

    @classmethod
    def goodbye(cls):
        print "goodbye daughter"

if __name__ == '__main__':
    son = SonObject()
    son.greetings()
    son.hello_son()
    son.goodbye()
    daughter = DaughterObject()
    daughter.greetings()
    daughter.hello_daughter()
    daughter.goodbye()

给定的代码输出如下:

greetings
hello son
goodbye son
greetings
hello daughter
goodbye daughter

我希望代码输出以下内容:

-smile_warmly - SonObject
greetings
-smile_warmly - SonObject
hello son
-smile_warmly - SonObject
goodbye son
-smile_warmly - DaughterObject
greetings
-smile_warmly - DaughterObject
hello daughter
-smile_warmly - DaughterObject
goodbye daughter

但我不想在每个方法之前添加行 @smile_warmly(当我尝试在上面的代码中这样做时,我收到错误消息 TypeError: 'classmethod' object is not callable)。相反,我希望在 __init__() 方法中以编程方式对每个方法进行装饰。

是否可以在 Python 中以编程方式装饰方法?

编辑:找到了一些似乎可行的东西——请参阅下面的答案。感谢 BrenBarn。

【问题讨论】:

标签: python decorator class-method


【解决方案1】:

装饰器所做的只是返回一个新函数。这个:

@deco
def foo():
    # blah

和这个是一样的:

def foo():
    # blah
foo = deco(foo)

您可以随时做同样的事情,无需@ 语法,只需将函数替换为您喜欢的任何内容即可。因此,在__init__ 或其他任何地方,您可以循环遍历所有方法,并将每个方法替换为smilewarmly(meth)

但是,与其在__init__ 中执行此操作,不如在创建类时执行此操作更有意义。您可以使用元类或更简单地使用类装饰器来做到这一点:

def smileDeco(func):
    def wrapped(*args, **kw):
        print ":-)"
        func(*args, **kw)
    return classmethod(wrapped)

def makeSmiley(cls):
    for attr, val in cls.__dict__.iteritems():
        if callable(val) and not attr.startswith("__"):
            setattr(cls, attr, smileDeco(val))
    return cls

@makeSmiley
class Foo(object):
    def sayStuff(self):
        print "Blah blah"

>>> Foo().sayStuff()
:-)
Blah blah

在这个例子中,我将 classmethod 装饰放在我的 smileDeco 装饰器中。你也可以把它放在makeSmiley 中,这样makeSmiley 返回smileDeco(classmethod(val))。 (您想采用哪种方式取决于微笑装饰器与作为类方法的事物的联系程度。)这意味着您不必在类中使用@classmethod

当然,在makeSmiley 的循环中,您可以包含任何您喜欢决定(例如,基于方法的名称)是否用微笑行为包装它的逻辑。

请注意,如果您真的想在类中手动使用@classmethod,则必须更加小心,因为通过类__dict__ 访问的类方法是不可调用的。所以你必须专门检查该对象是否是一个类方法对象,而不是仅仅检查它是否可调用。

【讨论】:

  • 这看起来很有帮助。我正在尝试让它立即工作,如果我能完成它,我会在此处提供更新。
  • 如果我有几十个类是我父类的子类,那么我必须记住装饰每个类。我宁愿让调整方法的逻辑在__init__() 方法中自动发生。我的子类不会覆盖 __init__(),所以父类中定义的__init__() 总是会在构造子类时被调用。如果我可以在那里放置类似的逻辑,那么我将拥有我想要的功能。所以这就是我正在做的事情。
  • @JonCrowell:你可以这样做,但是__init__ 被每个instance 调用,而不是每个类,所以它看起来不像你想要的。如果您希望行为完全自动化,则必须使用元类。您可以阅读元类 herehere
  • 我有一个类层次结构,我希望所有方法都是类方法。感谢您的指点,我几乎拥有了我想要的东西。请参阅我对原始问题的编辑。我会将您的答案标记为正确。
  • 在下面查看我的答案,它会产生我想要的输出。
【解决方案2】:

这个解决方案产生了我想要的输出:

class ParentObject(object):
    def __init__(self):
        self._adjust_methods(self.__class__)

    def _adjust_methods(self, cls):
        for attr, val in cls.__dict__.iteritems():
            if callable(val) and not attr.startswith("_"):
                setattr(cls, attr, self._smile_warmly(val))
        bases = cls.__bases__
        for base in bases:
            self._adjust_methods(base)

    def _smile_warmly(self, the_method):
        def _wrapped(self, *args, **kwargs):
            print "-smile_warmly - " +self.__name__
            the_method(self, *args, **kwargs)
        cmethod_wrapped = classmethod(_wrapped)
        # cmethod_wrapped.adjusted = True
        return cmethod_wrapped

    def greetings(self):
        print "greetings"

class SonObject(ParentObject):
    def hello_son(self):
        print "hello son"

    def goodbye(self):
        print "goodbye son"

class DaughterObject(ParentObject):
    def hello_daughter(self):
        print "hello daughter"

    def goodbye(self):
        print "goodbye daughter"

if __name__ == '__main__':
    son = SonObject()
    son.greetings()
    son.hello_son()
    son.goodbye()
    daughter = DaughterObject()
    daughter.greetings()
    daughter.hello_daughter()
    daughter.goodbye()

输出是:

-smile_warmly - SonObject
greetings
-smile_warmly - SonObject
hello son
-smile_warmly - SonObject
goodbye son
-smile_warmly - DaughterObject
greetings
-smile_warmly - DaughterObject
hello daughter
-smile_warmly - DaughterObject
goodbye daughter

【讨论】:

  • 如果你多次实例化一个类,这将导致问题,因为它会再次包装已经包装的方法。至少,您应该在包装每个类时为其设置一个标志,以将其标记为已包装。然后你可以在开始时检查标志,如果类已经被包装,则跳过包装。
  • 嗯 - 我想我可能搞砸了。我在这里给出的玩具示例给出了我想要的输出,但我的真实代码更复杂,给我一个令人困惑的TypeError: 'NoneType' object is not callable 消息并崩溃。
  • 您可能想针对该问题发布一个新问题。
  • 是的——即使现在我正在创建一个很好的例子来隔离问题。 :)
猜你喜欢
  • 2021-08-25
  • 2011-01-22
  • 2020-08-09
  • 2012-03-18
  • 2013-06-23
  • 1970-01-01
  • 2017-10-15
  • 1970-01-01
  • 2014-06-07
相关资源
最近更新 更多