你不能做你想要的只是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>
我能夠構建即使兩個foo
和bar
Concrete()
是簡單的屬性。
ABCMeta
元類僅跟蹤有多少個對象,__isabstractmethod__
屬性爲true;當從元類創建類時(調用ABCMeta.__new__
)cls.__abstractmethods__
屬性隨後設置爲frozenset
對象,並且所有名稱仍然是抽象的。
type.__new__
然後測試那個frozenset
並拋出TypeError
如果您嘗試創建一個實例。
您必須在此製作您的自己的__new__
方法;子類ABCMeta
,並在新的__new__
方法中添加類型檢查。該方法應在基類上尋找__abstractmethods__
集合,在MRO中查找具有__isabstractmethod__
屬性的相應對象,然後對當前類屬性進行類型檢查。
這意味着當定義類時,您會拋出異常,但不是實例。爲了達到這個目的,你需要在你的ABCMeta
子類中添加一個__call__
方法,然後根據你自己的__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>