Контекст
У меня есть приложение 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', (), {})
TypeError
выбрасывается в Python 2? - person Martijn Pieters   schedule 09.08.2014TypeError
. Именно в этом проблема: я хочу, чтобы один был выброшен, потому чтоMyABC
должен быть абстрактным. - person sirosen   schedule 09.08.2014type.__new__
; Я не вижу здесь никаких переопределений для__new__
; возможно,SomeParentABC
делает это? - person Martijn Pieters   schedule 09.08.2014with_metaclass
, который редактирует__new__
. Признаюсь, у меня голова болит от чтения. - person sirosen   schedule 09.08.2014