如何用python 2.6到3.5中的具体初始化器编写Python ABC?

时间:2014-08-09 17:11:57

标签: python python-2.7 python-3.x unit-testing abc

上下文

我有一个python应用程序,它具有相对复杂的类层次结构。它需要使用python 2.6到python 3.5(一个很大的范围,我知道!),并且我一直在使用ABCs的特殊问题。 我使用sixwith_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'

gamma中捆绑了很多MyABC类似函数,还有一些子类特定函数,如alphabeta。我希望MyABC的所有子类继承相同的__init__gamma属性,然后依靠自己的特定特征。

问题

问题是,为了让MyChild1MyChild2共享__init__的代码,MyABC需要有一个具体的初始值设定项。 在Python 3中,一切都运行得很好,但在Python 2中,当初始化程序具体时,我在实例化TypeErrors时无法获得MyABC

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

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没有任何其他抽象属性,但实例化具有gamma但没有alpha或{{beta的类是没有意义的。 1}}。 目前,我只是通过复制__init__MyChild1中的MyChild2功能来解决DRY违规问题,但随着时间的推移,这变得越来越繁重

如何在不使其可实例化的情况下为Python 2 ABC提供具体的初始化程序,同时保持Python 3的兼容性? 换句话说,我想尝试实例化MyABC以在Python 2和Python 3中抛出TypeError,但它只会在Python 3中抛出它们。

with_metaclass

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

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', (), {})

1 个答案:

答案 0 :(得分:2)

six.with_metaclass()元类可能与ABC不兼容,因为它会覆盖type.__new__;这可能会干扰测试混凝土方法的正常程序。

请尝试使用@six.add_metaclass() class decorator

来自abc import ABCMeta 来自六个导入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