【问题标题】:Python - why does this method return NoneType when I've checked that the object I'm returning is not NoneType?Python - 当我检查我返回的对象不是 NoneType 时,为什么这个方法返回 NoneType?
【发布时间】:2012-12-31 05:25:26
【问题描述】:

我的代码中所有对象都来自一个基础对象,我不打算直接实例化它。在我的基础对象的__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'

为什么会这样?

【问题讨论】:

    标签: python


    【解决方案1】:

    尝试改变:

        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 方法正在调用被包装的方法,但不返回该方法的返回值。

    【讨论】:

    • ...相反,它实际上是在返回 None,因为它在没有 return &lt;something&gt; 语句的情况下会落到最后。
    【解决方案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
    

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

      • 我明白你在说什么,但我已经检查过了,这些方法并没有被一遍又一遍地包裹起来。如果我创建了五个子对象,那么如果我在最后创建的对象上调用 hello_son(),我希望“-smile_warmly - SonObject”被打印五次,但这不会发生。它只打印一次。
      • 我确实想包装我班级的所有方法。在这个例子中它没有多大意义,但在我实际处理的代码中它更有意义。我不是打印“-smile_warmly”,而是检查某个条件并在不满足时抛出异常——并且这个检查需要对类中的每个方法进行。
      • @JonCrowell:啊,它没有包装它们,因为classmethod 对象本身是不可调用的。但是每次创建实例时,包装代码仍然会被不必要地调用。如果您在这些类上创建大量此类、实例或方法,则可能会在未来造成性能下降。
      【解决方案4】:

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

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

      【讨论】:

        猜你喜欢
        • 2021-03-12
        • 2015-08-18
        • 1970-01-01
        • 2022-07-12
        • 2012-12-01
        • 2020-06-04
        • 1970-01-01
        • 2019-01-12
        • 2021-06-24
        相关资源
        最近更新 更多