0

我有代码,其中所有对象都来自一个基础对象,我不打算直接实例化它。在__init__()我的基础对象的方法中,我试图执行一些魔法——我试图装饰或包装正在初始化的对象的每个方法。但是当我调用结果方法时,我得到的结果让我感到困惑。这是隔离问题的示例代码:

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:
            if base.__name__ != 'object':
                self._adjust_methods(base)

    def _smile_warmly(self, the_method):
        def _wrapped(cls, *args, **kwargs):
            print "\n-smile_warmly - " +cls.__name__
            the_method(self, *args, **kwargs)
        cmethod_wrapped = classmethod(_wrapped)
        return cmethod_wrapped

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

    def get_sister(self):
        sis = DaughterObject()
        print type(sis)
        return sis

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

    def get_brother(self):
        bro = SonObject()
        print type(bro)
        return bro

if __name__ == '__main__':
    son = SonObject()
    son.hello_son()

    daughter = DaughterObject()
    daughter.hello_daughter()

    sis = son.get_sister()
    print type(sis)
    sis.hello_daughter()

    bro = sis.get_brother()
    print type(bro)
    bro.hello_son()

然而,程序崩溃了——该行sis = son.get_sister()导致sis对象的类型为 NoneType。这是输出:

-smile_warmly - SonObject
hello son

-smile_warmly - DaughterObject
hello daughter

-smile_warmly - SonObject
<class '__main__.DaughterObject'>
<type 'NoneType'>
Traceback (most recent call last):
  File "metaclass_decoration_test.py", line 48, in <module>
    sis.hello_daughter()
AttributeError: 'NoneType' object has no attribute 'hello_daughter'

为什么会这样?

4

4 回答 4

4

尝试改变:

    def _wrapped(cls, *args, **kwargs):
        print "\n-smile_warmly - " +cls.__name__
        the_method(self, *args, **kwargs)

    def _wrapped(cls, *args, **kwargs):
        print "\n-smile_warmly - " +cls.__name__
        return the_method(self, *args, **kwargs)

您的_wrapped方法正在调用正在包装的方法,但不返回该方法的返回值。

于 2012-12-31T05:29:26.753 回答
2

好吧,我什至不想触及这段代码中发生的疯狂,但您的错误具体是因为您的“装饰器”没有从包装函数返回任何内容:

def _smile_warmly(self, the_method):
    def _wrapped(cls, *args, **kwargs):
        print "\n-smile_warmly - " +cls.__name__
        return the_method(self, *args, **kwargs) # return here
    cmethod_wrapped = classmethod(_wrapped)
    return cmethod_wrapped
于 2012-12-31T05:35:41.533 回答
1

@PaulMcGuire 的回复中缺少返回是该错误的原因。

在更高的层次上,您似乎正在尝试通过继承来做可能更“常见”(这几乎不是一种常见的方法)通过元类完成的事情。也许像这个关于元类的讨论会为你指明一个更易于管理的方向。

于 2012-12-31T06:35:02.387 回答
1

问题是您正在包装类的所有方法,包括get_sister. 您可以按照@Paul McGuire 的建议进行操作并将其添加return到包装器中,但这意味着当您调用时会打印“微笑”消息son.get_sister,这可能不是您想要的。

相反,您可能需要做的是在内部添加一些逻辑_adjust_methods来精确决定要包装哪些方法。除了检查callableandnot startswith('_')之外,您还可以为您想要或不想用smile行为包装的命名约定。但是,您这样做的次数越多,与仅手动装饰您要装饰的方法相比,自动装饰对您的好处就越少。有点难以理解为什么要使用显然要使用的结构(所有类方法,包装所有内容等)。也许如果你在这里解释你的最终目标是什么,有人可以建议一个更直接的设计。

此外,即使您添加return或额外的包装逻辑,您仍然会遇到我在另一个问题中提到的问题:由于您进行包装__init__,每次实例化一个类时都会发生这种情况,所以您将继续添加越来越多的包装器。这就是为什么我在那里建议您应该使用类装饰器,或者,如果必须的话,使用元类。弄乱类属性(包括方法)__init__不是一个好主意,因为它们会一遍又一遍地弄乱,对于您创建的每个实例一次。

于 2012-12-31T05:38:32.917 回答