4

如果某个类扩展了 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 类的类提供自动委托的一些其他方法。

PS请不要class MyElement(StandardElement)在这里建议我继承()作为解决方案......上面提供的代码只是一个示例。在我的真实案例中,MyElement 与 StandardElement 相比完全不同。不过,我必须使 MyElement 与 StandardElement 兼容,因为有时有人应该使用 MyElement 而不是 StandardElement。我真的需要在这里实现“有”关系,而不是“是”。

4

3 回答 3

4

默认情况下,没有自动方式来执行您想要的委派。委托实际上并不是抽象类的显式部分,因此这并不奇怪。但是,您可以编写自己的委托元类,为您添加缺少的方法:

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在函数创建后不会更改的命名空间。__new__在 Python 3 中,您可以通过提供具有默认值的仅关键字参数在方法中完成所有操作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设置由元类创建的方法将使用的对象。如果你愿意,你可以把它做成某种方法,但这似乎是最简单的方法。

于 2016-12-24T02:46:30.057 回答
0

根据 Blckknght 的回答,我创建了一个 python 包,扩展了多个委托选项和自定义委托属性名称的功能。

在这里查看https://github.com/monomonedula/abc-delegation

安装:pip install abc-delegation

于 2019-11-17T10:12:45.973 回答
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()
于 2016-12-24T02:15:11.277 回答