你不能做你想做的事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
元类仅跟踪属性为 trueABCMeta
的剩余对象数量;__isabstractmethod__
当从元类(ABCMeta.__new__
被调用)创建一个类时,该cls.__abstractmethods__
属性被设置为一个frozenset
对象,其所有名称仍然是抽象的。
type.__new__
然后对其进行测试并在您尝试创建实例时frozenset
抛出 a 。TypeError
您必须在这里生成自己的 __new__
方法;子类ABCMeta
并在新__new__
方法中添加类型检查。该方法应该__abstractmethods__
在基类上查找集合,在 MRO 中找到具有该__isabstractmethod__
属性的相应对象,然后对当前类属性进行类型检查。
这意味着您在定义class时会抛出异常,而不是实例。为此,您需要向子类添加一个__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>