如何在Python 2.6到3.5中编写带有具体初始化式的Python ABC



背景信息

我有一个具有相对复杂的类层次结构的python应用程序。它需要与python 2.6到python 3.5一起工作(我知道范围很大!),而且我在使用abc时遇到了一些特殊的问题。我正在使用six库的with_metaclass来减轻一些伤害,但它仍然有问题。

一组特殊的课程一直给我带来麻烦。下面是它的简化形式:

from abc import ABCMeta
from six import with_metaclass
# SomeParentABC is another ABC, in case it is relevant
class MyABC(with_metaclass(ABCMeta, SomeParentABC)):
    def __init__(self, important_attr):
        self.important_attr = important_attr
    def gamma(self):
         self.important_attr += ' gamma'
class MyChild1(MyABC):
    def __repr__(self):
        return "MyChild1(imporant_attr=%s)" % important_attr
    def alpha(self):
         self.important_attr += ' alpha'
class MyChild2(MyABC):
    def __repr__(self):
        return "MyChild2(imporant_attr=%s)" % important_attr
    def beta(self):
         self.important_attr += ' beta'

MyABC中捆绑了大量的gamma类函数,以及alphabeta等少数子类专用函数。我希望MyABC的所有子类都继承相同的__init__gamma属性,然后堆叠自己的特定特征。

<标题>

问题是,为了让MyChild1MyChild2共享__init__的代码,MyABC需要有一个具体的初始化式。在Python 3中,一切都工作得很好,但在Python 2中,当初始化器是具体的时,我在实例化MyABC时无法获得TypeErrors

我的测试套件中有一个片段,看起来像这样

def test_MyABC_really_is_abstract():
    try:
        MyABC('attr value')
    # ideally more sophistication here to get the right kind of TypeError,
    # but I've been lazy for now
    except TypeError:
        pass
    else:
        assert False

不知何故,在Python 2.7(我假设是2.6,但没有费心检查)中,此测试失败。

MyABC没有任何其他抽象属性,但是在没有alphabeta的情况下实例化具有gamma的类是没有意义的。现在,我已经通过在MyChild1MyChild2中复制__init__函数来通过DRY违规,但随着时间的推移,这变得越来越繁重。

我如何给Python 2 ABC一个具体的初始化器而不使其可实例化,同时保持Python 3的兼容性?换句话说,我想尝试在Python 2和Python 3中实例化MyABC以抛出TypeError s,但它只在Python 3中抛出它们。

with_metaclass

我相信在这里看到with_metaclass的代码是相关的。这是根据six项目的现有许可和版权提供的,(c) 2010-2014

def with_metaclass(meta, *bases):
    """Create a base class with a metaclass."""
    # This requires a bit of explanation: the basic idea is to make a dummy
    # metaclass for one level of class instantiation that replaces itself with
    # the actual metaclass.
    class metaclass(meta):
        def __new__(cls, name, this_bases, d):
            return meta(name, bases, d)
    return type.__new__(metaclass, 'temporary_class', (), {})

six.with_metaclass()元类可能与abc不兼容,因为它覆盖了type.__new__;这可能会干扰对具体方法进行正常测试的程序。

尝试使用@six.add_metaclass()类装饰器:

从abc导入ABCMetaimport add_metaclass

@add_metaclass(ABCMeta)
class MyABC(SomeParentABC):
    def __init__(self, important_attr):
        self.important_attr = important_attr
    def gamma(self):
         self.important_attr += ' gamma'
演示:

>>> from abc import ABCMeta, abstractmethod
>>> from six import add_metaclass
>>> @add_metaclass(ABCMeta)
... class MyABC(object):
...     @abstractmethod
...     def gamma(self): pass
... 
>>> MyABC()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class MyABC with abstract methods gamma

请注意,您确实需要有抽象方法而不需要具体实现来引发TypeError !

最新更新