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

0 投票
1 回答
880 浏览
提问于 2025-04-18 16:40

背景

我有一个用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这样的函数,还有一些子类特有的函数,比如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__函数来解决这个问题,但随着时间的推移,这变得越来越麻烦。

我该如何给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 个回答

2

这个 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 错误!

撰写回答