2016-12-24 120 views
2

如果某些类扩展了abc类(抽象基类),那么除非定义了所有抽象方法,否则我不能实例化它。但通常在实现装饰模式时,我只想定义一些抽象方法和其他方法 - 只是委托给装饰对象。这个怎么做?自动委派扩展python abc类的类

例如,我想使下面的代码工作:的

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),所以出现此错误。抽象类是一个至少有一个抽象方法的类。为了解决这个错误,你应该以这种方式添加此方法,例如:如果你想使用__getattr__似乎通过abc.abstractmethod是不可能下放许多方法

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__动态搜索必要的方法是没有__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不允许实例化并非所有abstractmethods类的类 – 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中,通过给delegator提供一个仅具有默认值的关键字参数,但是在Python 2中没有关键字唯一参数,您可以在__new__方法中执行所有操作,因此这对您不起作用。

这里是你如何使用它:

class MyElement(IElement): 
    __metaclass__ = DelegatingMeta 

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

    def click(self): 
     return "my click" 

分配给self._delegate设置由元类创建的方法将使用对象。如果你想,你可以做出某种方法,但这似乎是最简单的方法。

+0

嗯......看起来像我需要的东西:)你说得对,我可以把你的解决方案包装在我原来的例子中的某种委托方法中。谢谢!我会尝试使用它。 – yashaka

+0

你真的需要为此使用元类,你不能在'__init__'中调用'delegate'方法,因为'__init__'只在实例创建后运行,并且你不能实例化一个抽象类。使用元类可以在创建类之前添加委托方法,从而允许类实例化。现在,设置委托方法使用的对象的属性,确保可以在'delegate()'函数或方法中。但可能没有必要(只需设置一个属性更容易)。 – Blckknght

+0

噢,这是我自己写的,对多重继承的处理可能是不正确的。它应该像为单一继承而写的那样工作,并且对于多个继承而言,不同的基础从不与抽象方法交互。在一个基地具有在另一个基地具体实施的仍然抽象的方法的情况下,它会出错。解决这个问题并非不可能,但现在比我有时间做更多的努力。 – Blckknght