如何在Python 2.6到3.5中编写带具体初始化器的ABC?
背景
我有一个用Python写的应用程序,里面有一个比较复杂的类结构。这个程序需要兼容Python 2.6到Python 3.5(我知道这个范围很大!),而且我在使用抽象基类(ABCs)时遇到了一些特别的问题。
我正在使用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
这样的函数,还有一些子类特有的函数,比如alpha
和beta
。我希望所有MyABC
的子类都能继承相同的__init__
和gamma
属性,然后再添加它们自己的特性。
问题
问题在于,为了让MyChild1
和MyChild2
共享__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
没有其他抽象属性,但如果没有alpha
或beta
,实例化一个有gamma
的类是没有意义的。
现在,我只能通过在MyChild1
和MyChild2
中重复__init__
函数来解决这个问题,但随着时间的推移,这变得越来越麻烦。
我该如何给Python 2的抽象基类(ABC)一个具体的初始化方法,而又不让它可以被实例化,同时保持与Python 3的兼容性?换句话说,我希望在Python 2和Python 3中尝试实例化MyABC
时都能抛出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 个回答
这个 six.with_metaclass()
元类可能和 ABC(抽象基类)不兼容,因为它覆盖了 type.__new__
;这可能会影响正常检查具体方法的过程。
你可以试试使用这个 @six.add_metaclass()
类装饰器:
从 abc 导入 ABCMeta
从 six 导入 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
错误!