Python类继承 - 怪异的动作

时间:2014-12-31 11:15:48

标签: python inheritance multiple-inheritance

我观察到了类继承的奇怪效果。对于我正在进行的项目,我创建了一个类来充当另一个模块类的包装器。

我正在使用第三方aeidon模块(用于操作字幕文件),但问题可能不那么具体。

以下是您通常使用模块的方式......

project = aeidon.Project()
project.open_main(path)

以下是示例'包装器'正在使用的类(当然真正的类有很多方法):

class Wrapper(aeidon.Project):
    pass

project = Wrapper()
project.open_main(path)

上述代码在执行时引发 AttributeError 。但是,以下工作正如我原先预期的那样:

junk = aeidon.Project()
project = Wrapper()
project.open_main(path)

我在远距离的幽灵行动之后将这个问题命名为因为我怀疑它涉及环境中的全球变量/物体,但我不知道。

我最终使用合成来解决这个问题(即self.project = aeidon.Project()),但我仍然对此感到好奇。谁能解释一下这里发生了什么?

这是追溯:

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-5-fe548abd7ad0> in <module>()
----> 1 project.open_main(path)

/usr/lib/python3/dist-packages/aeidon/deco.py in wrapper(*args, **kwargs)
    208     def wrapper(*args, **kwargs):
    209         frozen = args[0].freeze_notify()
--> 210         try: return function(*args, **kwargs)
    211         finally: args[0].thaw_notify(frozen)
    212     return wrapper

/usr/lib/python3/dist-packages/aeidon/agents/open.py in open_main(self, path, encoding)
    161         format = aeidon.util.detect_format(path, encoding)
    162         self.main_file = aeidon.files.new(format, path, encoding)
--> 163         subtitles = self._read_file(self.main_file)
    164         self.subtitles, sort_count = self._sort_subtitles(subtitles)
    165         self.set_framerate(self.framerate, register=None)

/usr/lib/python3/dist-packages/aeidon/project.py in __getattr__(self, name)
    116             return self._delegations[name]
    117         except KeyError:
--> 118             raise AttributeError
    119 
    120     def __init__(self, framerate=None, undo_limit=None):

AttributeError:

无论是否打电话给Project __init__(),我都试过了。显然,这不是真正应该在正常情况下完成的事情,我只是困惑为什么Wrapper()只有在创建一个垃圾aeidon.Project()之后才能按预期运行。

1 个答案:

答案 0 :(得分:3)

aedion.project module做了两件事:

  • 它将aedion.agents包中的类添加到类中,以便文档生成器在使用ProjectMeta metaclass提取文档字符串和其他信息时包含这些方法:

    class ProjectMeta(type):
    
        """
        Project metaclass with delegated methods added.
        Public methods are added to the class dictionary during :meth:`__new__`
        in order to fool Sphinx (and perhaps other API documentation generators)
        into thinking that the resulting instantiated class actually contains those
        methods, which it does not since the methods are removed during
        :meth:`Project.__init__`.
        """
    

    然而,如果使用这些方法,则无法正确绑定。

  • Project.__init__方法调用Project._init_delegations()。此方法从类中删除委派的方法:

    # Remove class-level function added by ProjectMeta.
    if hasattr(self.__class__, attr_name):
        delattr(self.__class__, attr_name)
    

    请注意此处使用self.__class__。这是必需的,因为如果在类上找到方法,Python不会通过__getattr__钩子查找委托方法。

    委托方法绑定到专用代理程序实例,因此事实上委托给该代理程序:

    agent = getattr(aeidon.agents, agent_class_name)(self)
    # ...
    attr_value = getattr(agent, attr_name)
    # ...
    self._delegations[attr_name] = attr_value
    

在此类周围创建包装器时,删除步骤将失败。 self.__class__是您的包装器,而不是基础Project类。因此,方法绑定不正确;绑定了元类提供的方法,并且永远不会调用__getattr__挂钩来查找委派的方法:

>>> import aeidon
>>> class Wrapper(aeidon.Project): pass
... 
>>> wrapper = Wrapper()
>>> wrapper.open_main
<bound method Wrapper.open_main of <__main__.Wrapper object at 0x1106313a8>>
>>> wrapper.open_main.__self__
<__main__.Wrapper object at 0x1106313a8>
>>> wrapper._delegations['open_main']
<bound method OpenAgent.open_main of <aeidon.agents.open.OpenAgent object at 0x11057e780>>
>>> wrapper._delegations['open_main'].__self__
<aeidon.agents.open.OpenAgent object at 0x11057e780>

因为open_main上的Project方法仍然存在:

>>> Project.open_main
<function OpenAgent.open_main at 0x110602bf8>

只要您创建Project的实例,就会从类中删除这些方法:

>>> Project()
<aeidon.project.Project object at 0x1106317c8>
>>> Project.open_main
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'Project' has no attribute 'open_main'

并且您的包装器将开始工作,因为现在找到了委派的open_main

>>> wrapper.open_main
<bound method OpenAgent.open_main of <aeidon.agents.open.OpenAgent object at 0x11057e780>>
>>> wrapper.open_main.__self__
<aeidon.agents.open.OpenAgent object at 0x11057e780>

你的包装器必须自己进行删除:

class Wrapper(aeidon.Project):
    def __init__(self):
        super().__init__()
        for name in self._delegations:
            if hasattr(aeidon.Project, name):
                delattr(aeidon.Project, name)

请注意,如果aedion维护者仅使用self.__class__替换__class__(无self),那么他们的代码仍可以使用您的子类方法也可以在不必再次手动清理类的情况下工作。这是因为在Python 3中,方法中的__class__引用是一个自动闭包变量,指向定义方法的类。 Project._init_delegations() Project。{{1}}。也许您可以提交错误报告。