2016-12-24 6 views
2

Если какой-либо класс расширяет класс abc (абстрактный базовый класс), то я не могу его создать, если я не определяю все абстрактные методы. Но часто при реализации шаблона Decorator я хочу определить только несколько абстрактных методов, а другие - просто делегировать декорированный объект. Как это сделать?Автоматическое делегирование для классов, расширяющих классы abc класса python

Например, я хочу сделать следующий код работы:

from abc import ABCMeta, abstractmethod 


class IElement(object): 
    __metaclass__ = ABCMeta 

    @abstractmethod 
    def click(self): 
     return 

    @abstractmethod 
    def hover(self): 
     return 

    # ... more IElement's abstractmethods... 


class StandardElement(IElement): 

    def click(self): 
     return "click" 

    def hover(self): 
     return "hover" 

    # ... more implemented IElement's methods... 


class MyElement(IElement): 
    def __init__(self, standard_element): 
     self._standard_element = standard_element 
     delegate(IElement, standard_element) 

    def click(self): 
     return "my click" 


assert MyElement(StandardElement()).click() == 'my click' 
assert MyElement(StandardElement()).hover() == 'click' 

вместо

from abc import ABCMeta, abstractmethod 


class IElement(object): 
    __metaclass__ = ABCMeta 

    @abstractmethod 
    def click(self): 
     return 

    @abstractmethod 
    def hover(self): 
     return 

    # ... more IElement's abstractmethods... 


class StandardElement(IElement): 

    def click(self): 
     return "click" 

    def hover(self): 
     return "hover" 

    # ... more implemented IElement's methods... 


class MyElement(IElement): 
    def __init__(self, standard_element): 
     self._standard_element = standard_element 

    def click(self): 
     return "my click" 

    def hover(self): 
     return self._standard_element.hover() 

    # ... more manually delegated IElement's methods to self._standard_element object, aggregated in initialiser... 


assert MyElement(StandardElement()).click() == 'my click' 
assert MyElement(StandardElement()).hover() == 'click' 

Для этого мне нужно реализовать delegate метод из примера выше. Как его реализовать? Также можно рассмотреть некоторые другие подходы к обеспечению автоматического делегирования для классов, расширяющих классы abc.

P.S. Пожалуйста, не предлагайте мне Наследование (class MyElement(StandardElement)) в качестве решения здесь ... Приведенный выше код является лишь примером. В моем реальном случае MyElement - это совсем другое по сравнению с StandardElement. Тем не менее, я должен сделать MyElement совместимым с StandardElement, потому что иногда кто-то должен использовать MyElement вместо StandardElement. Мне действительно нужно реализовать «есть» связь здесь, а не «есть».

+0

Является ли 'StandardElement' предположенным абстрактным классом? – Fomalhaut

+0

Нет.StandardElement - это реализация IElement. IElement является абстрактным классом ... – yashaka

ответ

0

Вы получаете эту ошибку, потому что класс MyElement является абстрактным (вы не переопределили метод hover). Абстрактный класс - это класс, который имеет хотя бы один абстрактный метод. Для устранения ошибки необходимо добавить этот метод таким образом, например:

class MyElement(IElement): 
    def __init__(self, standard_element): 
     self._standard_element = standard_element 

    def click(self): 
     return "my click" 

    def hover(self): 
     return self._standard_element.hover() 

Если вы хотите делегировать многие методы использования __getattr__, кажется, невозможно с помощью abc.abstractmethod, потому что __getattr__ динамически ищет необходимые методы, невозможно проверить без прямого запуска __getattr__.

Поэтому я бы рекомендовал вам указать все методы как абстрактные (через @abstractmethod), которые наверняка будут переопределены в каждом унаследованном классе и реализованы делегированный метод с использованием NotImplementedError. Пример:

class IElement(object): 
    __metaclass__ = ABCMeta 

    @abstractmethod 
    def necessary_to_implement_explicitely(self): 
     pass 

    def click(self): 
     raise NotImplementedError() 

    def hover(self): 
     raise NotImplementedError() 
+0

Добро пожаловать. Решает ли ваша проблема? – Fomalhaut

+0

Нет :) Даже с такой «классической» делегией делегирования: https://www.refheap.com/124379. И это очевидно. Это не работает, потому что использование abc запрещает создавать классы с не всеми абстрактными методами overriden – yashaka

+0

Хорошо, теперь я понял, что вы имеете в виду. Через несколько минут я отредактирую ответ. – Fomalhaut

3

Автоматический способ сделать нужную делегацию по умолчанию не существует. Делегирование на самом деле не является явной частью абстрактных классов, поэтому это не должно быть неожиданностью. Однако вы можете написать свои собственные Делегирование метакласса, что добавляет недостающие методы для вас:

def _make_delegator_method(name): 
    def delegator(self, *args, **kwargs): 
     return getattr(self._delegate, name)(*args, **kwargs) 
    return delegator 

class DelegatingMeta(ABCMeta): 
    def __new__(meta, name, bases, dct): 
     abstract_method_names = frozenset.union(*(base.__abstractmethods__ 
                for base in bases)) 
     for name in abstract_method_names: 
      if name not in dct: 
       dct[name] = _make_delegator_method(name) 

     return super(DelegatingMeta, meta).__new__(meta, name, bases, dct) 

доверителя методы выполнены в виде отдельной функции, потому что нам нужно пространство имен, где name не меняется после того, как это было создано функция. В Python 3 вы можете сделать все в методе __new__, указав delegator аргумент только для ключевого слова со значением по умолчанию, но в Python 2 нет аргументов только для ключевого слова, так что это не сработает для вас.

Вот как вы бы использовать:

class MyElement(IElement): 
    __metaclass__ = DelegatingMeta 

    def __init__(self, standard_element): 
     self._delegate = standard_element 

    def click(self): 
     return "my click" 

Присвоение self._delegate задает объект методы, созданные метакласса будет использовать. Если бы вы этого захотели, вы могли бы сделать такой метод, но это был самый простой подход.

+0

Ммм ... Это похоже на то, что мне нужно :) Вы правы, я могу обернуть ваше решение каким-то методом 'delegate' из моего первоначального примера. Спасибо! Я попытаюсь использовать его. – yashaka

+0

Вам действительно нужно использовать метакласс для этого, вы не можете вызвать метод 'delegate' в' '__init__', потому что' __init__' запускается только после создания экземпляра, и вы не можете создать экземпляр абстрактного класса. Используя метакласс, вы можете добавить методы делегирования, прежде чем создавать класс, который позволяет создавать экземпляр класса. Теперь, настраивая атрибут объекта, который используют методы делегата, уверен, что он может быть в функции или методе 'delegate(). Но, вероятно, нет необходимости (просто настройка атрибута проще). – Blckknght

+0

О, и это произошло со мной, так как я написал это, что обработка множественного наследования, вероятно, неверна. Он должен работать как написанный для одиночного наследования, так и для множественного наследования, когда разные базы никогда не взаимодействуют в отношении абстрактных методов. Это будет неправильно в ситуациях, когда одна база имеет абстрактный метод, который конкретно реализован на другой базе. Исправление проблемы не невозможно, но больше усилий, чем у меня есть время на данный момент. – Blckknght