2014-12-31 2 views
2

Я наблюдал странный эффект с наследованием класса. Для проекта, над которым я работаю, я создаю класс для работы в качестве оболочки для класса другого модуля.Наследование класса Python - призрачное действие

Я использую сторонний модуль 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) 

Я назвал этот вопрос после жутких действий на расстояние, потому что я подозреваю, что она включает в себя глобальные VARS/объекты в окружающей среде, но я не знаю.

В итоге я использовал композицию для решения этой проблемы (то есть 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: 

Я пробовал так и без вызова принадлежность проекта __init__(). Очевидно, что это не совсем то, что нужно делать при нормальных обстоятельствах, я просто недоумеваю, почему Wrapper() будет функционировать так, как ожидалось, только после создания нежелательной aeidon.Project().

+0

Это, вероятно, относится к проекту 'aeidon', но мы не можем сказать, потому что вы не включили полную трассировку. –

+0

ipython3 v2.3.1, но я также тестировал его в python3 v3.4.0 – Six

ответ

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' 

и вашей Wrap за бы начать работать, как сейчас делегированное 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. Возможно, вы можете отправить отчет об ошибке.

Смежные вопросы