Как написать Python ABC с конкретным инициализатором в Python 2.6 до 3.5?

Контекст

У меня есть приложение на 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'

Там много gamma как функции в комплекте MyABCи несколько специфических функций подкласса, таких как alpha а также beta, Я хочу, чтобы все подклассы MyABC наследовать то же самое __init__ а также gamma атрибуты, а затем накапливать свои специфические характеристики.

Эта проблема

Проблема в том, что для MyChild1 а также MyChild2 поделиться кодом для __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, На данный момент я справляюсь с СУХИМЫМ нарушением, просто дублируя __init__ функция в MyChild1 а также MyChild2, но со временем это становится все более и более обременительным.

Как я могу дать Python 2 ABC конкретный инициализатор, не делая его инстанцируемым при сохранении совместимости с Python 3? Другими словами, я хочу попытаться создать экземпляр MyABC бросать TypeErrors в Python 2 и Python 3, но он выбрасывает их только в Python 3.

with_metaclass

Я считаю, что важно видеть код для with_metaclass Вот. Это предусмотрено действующей лицензией и авторским правом six проект, (с) 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', (), {})

1 ответ

Решение

six.with_metaclass() метакласс может быть несовместим с ABC, поскольку он переопределяет type.__new__; это может помешать нормальной процедуре тестирования для конкретных методов.

Попробуйте использовать @six.add_metaclass()вместо декоратора класса:

из abc импорт 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 быть воспитанным!

Другие вопросы по тегам