Вы не можете делать то, что хотите, используя только ABCMeta
. Применение ABC не выполняет никаких проверок типов, принудительно применяется только наличие атрибута с правильным именем.
Возьмем, к примеру:
>>> from abc import ABCMeta, abstractmethod, abstractproperty
>>> class Abstract(object):
... __metaclass__ = ABCMeta
... @abstractmethod
... def foo(self): pass
... @abstractproperty
... def bar(self): pass
...
>>> class Concrete(Abstract):
... foo = 'bar'
... bar = 'baz'
...
>>> Concrete()
<__main__.Concrete object at 0x104b4df90>
Мне удалось построить Concrete()
, несмотря на то, что и foo
, и bar
являются простыми атрибутами.
Метакласс ABCMeta
отслеживает только то, сколько объектов осталось с атрибутом __isabstractmethod__
, имеющим значение true; при создании класса из метакласса (вызывается ABCMeta.__new__
) атрибут cls.__abstractmethods__
затем устанавливается в объект frozenset
со всеми именами, которые все еще являются абстрактными.
Затем type.__new__
проверяет этот frozenset
и выдает TypeError
, если вы пытаетесь создать экземпляр.
Здесь вам придется создать свой собственный __new__
метод; подкласс ABCMeta
и добавить проверку типов в новый метод __new__
. Этот метод должен искать наборы __abstractmethods__
в базовых классах, находить соответствующие объекты с атрибутом __isabstractmethod__
в MRO, а затем выполнять проверку типов для атрибутов текущего класса.
Это означало бы, что вы вызовете исключение при определении класса, а не экземпляра. Чтобы это работало, вы должны добавить метод __call__
в свой подкласс ABCMeta
и заставить его генерировать исключение на основе информации, собранной вашим собственным методом __new__
о том, какие типы были неправильными; аналогичный двухэтапный процесс, который сейчас делают ABCMeta
и type.__new__
. В качестве альтернативы, обновите набор __abstractmethods__
для класса, чтобы добавить все имена, которые были реализованы, но с неправильным типом, и оставьте значение type.__new__
для создания исключения.
Следующая реализация использует этот последний подход; добавьте имена обратно в __abstractmethods__
, если реализованный тип не соответствует (используя сопоставление):
from types import FunctionType
class ABCMetaTypeCheck(ABCMeta):
_typemap = { # map abstract type to expected implementation type
abstractproperty: property,
abstractstatic: staticmethod,
# abstractmethods return function objects
FunctionType: FunctionType,
}
def __new__(mcls, name, bases, namespace):
cls = super(ABCMetaTypeCheck, mcls).__new__(mcls, name, bases, namespace)
wrong_type = set()
seen = set()
abstractmethods = cls.__abstractmethods__
for base in bases:
for name in getattr(base, "__abstractmethods__", set()):
if name in seen or name in abstractmethods:
continue # still abstract or later overridden
value = base.__dict__.get(name) # bypass descriptors
if getattr(value, "__isabstractmethod__", False):
seen.add(name)
expected = mcls._typemap[type(value)]
if not isinstance(namespace[name], expected):
wrong_type.add(name)
if wrong_type:
cls.__abstractmethods__ = abstractmethods | frozenset(wrong_type)
return cls
С помощью этого метакласса вы получите ожидаемый результат:
>>> class Abstract(object):
... __metaclass__ = ABCMetaTypeCheck
... @abstractmethod
... def foo(self): pass
... @abstractproperty
... def bar(self): pass
... @abstractstatic
... def baz(): pass
...
>>> class ConcreteWrong(Abstract):
... foo = 'bar'
... bar = 'baz'
... baz = 'spam'
...
>>> ConcreteWrong()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class ConcreteWrong with abstract methods bar, baz, foo
>>>
>>> class ConcreteCorrect(Abstract):
... def foo(self): return 'bar'
... @property
... def bar(self): return 'baz'
... @staticmethod
... def baz(): return 'spam'
...
>>> ConcreteCorrect()
<__main__.ConcreteCorrect object at 0x104ce1d10>
person
Martijn Pieters
schedule
05.04.2015