Как написать 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'

В MyABC включено множество функций, подобных gamma, и несколько специфичных для подкласса функций, таких как 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. На данный момент я обходился нарушением DRY, просто дублируя функцию __init__ в MyChild1 и MyChild2, но со временем это становится все более и более обременительным.

Как я могу дать Python 2 ABC конкретный инициализатор, не делая его экземпляром, сохраняя при этом совместимость с Python 3? Другими словами, я хочу попытаться создать экземпляр MyABC для создания TypeError в Python 2 и Python 3, но он выдает их только в 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', (), {})

person sirosen    schedule 09.08.2014    source источник
comment
Какова полная трассировка, когда TypeError выбрасывается в Python 2?   -  person Martijn Pieters    schedule 09.08.2014
comment
@MartijnPieters Я не уверен, что вы здесь просите. В Python2 нет TypeError. Именно в этом проблема: я хочу, чтобы один был выброшен, потому что MyABC должен быть абстрактным.   -  person sirosen    schedule 09.08.2014
comment
Верно, тогда это было неясно; вы ожидаете, что он будет брошен, но это не так.   -  person Martijn Pieters    schedule 09.08.2014
comment
Этим занимается type.__new__; Я не вижу здесь никаких переопределений для __new__; возможно, SomeParentABC делает это?   -  person Martijn Pieters    schedule 09.08.2014
comment
@MartijnPieters Я только что отредактировал вопрос, включив в него код with_metaclass, который редактирует __new__. Признаюсь, у меня голова болит от чтения.   -  person sirosen    schedule 09.08.2014


Ответы (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!

person Martijn Pieters    schedule 09.08.2014
comment
Это определенно поможет, если правильное направление, но я все еще могу создать экземпляр класса, который использовал @add_metaclass(ABCMeta). В частности, если я украшаю add_metaclass и наследую от object, то MyABC('some value') все еще работает. - person sirosen; 09.08.2014
comment
@sirosen: я случайно ушел в вызове with_metaclass(); удален сейчас. - person Martijn Pieters; 09.08.2014
comment
@sirosen: при этом у меня возникли проблемы с воспроизведением вашего случая с six.with_metaclass. - person Martijn Pieters; 09.08.2014
comment
Я не думаю, что использование with_metaclass было проблемой. В python 2.7 и 3.2 (мои локальные тестовые версии) я все еще могу создать экземпляр MyABC, если он идентичен приведенному выше, но наследуется непосредственно от object. Нужно ли иметь абстрактное свойство или абстрактный метод для повышения TypeError? (Кроме того, большое спасибо за потраченное на это столько времени и усилий!) - person sirosen; 09.08.2014
comment
@sirosen: да, я предполагал, что они есть в вашей родительской мета-мета ABC. - person Martijn Pieters; 09.08.2014