强制所有继承类实现某方法
我遇到了一种情况,我想强制每一个从某个(抽象)类继承的类都必须实现一个方法。通常我会用 @abstractmethod 来做到这一点。不过,考虑到多重继承的情况:
from abc import ABCMeta, abstractmethod
class A(object):
__metaclass__ = ABCMeta
@abstractmethod
def very_specific_method(self):
pass
class B(A):
def very_specific_method(self):
print 'doing something in B'
class C(B):
pass
我还想强制 C
也实现这个方法。我希望每一个直接或间接继承自 A 的类都必须实现这个方法。这样做可以吗?
补充说明:我想这个要求只针对一个特定的方法,而不是所有的抽象方法。抽象方法应该继续按原来的方式工作,但也许可以创建一个新的装饰器来标识不同类型的方法。
顺便提一下:我在问题中提到 abc 是因为这似乎与这个问题最相关。我知道抽象方法通常是怎么工作的,并且经常使用它们。这是一个不同的情况,我不介意如果不是通过 abc 来实现。
2 个回答
3
我觉得这可能不是个好主意,但我觉得你可以这样做。可以看看这个ABCMeta
的实现,也许能给你一些灵感:
from abc import ABCMeta
def always_override(func):
func._always_override = True
return func
class always_override_property(property):
_always_override = True
class CrazyABCMeta(ABCMeta):
def __new__(mcls, name, bases, namespace):
cls = super(ABCMeta, mcls).__new__(mcls, name, bases, namespace)
abstracts = set()
# first, get all abstracts from the base classes
for base in bases:
abstracts.update(getattr(base, "_all_always_override", set()))
all_abstracts = abstracts.copy()
# Now add abstracts from this class and remove abstracts that this class defines
for name, value in namespace.items():
always_override = getattr(value, '_always_override', False)
if always_override:
abstracts.add(name)
all_abstracts.add(name)
elif name in abstracts:
abstracts.remove(name)
cls._all_always_override = frozenset(all_abstracts)
cls._always_override = frozenset(abstracts)
return cls
def __call__(cls, *args, **kwargs):
if cls._always_override:
raise TypeError(
'The following methods/properties must '
'be overridden {}'.format(cls._all_always_override))
return super(CrazyABCMeta, cls).__call__(*args, **kwargs)
# # # # # # # # # # #
# TESTS!
# # # # # # # # # # #
class A(object):
__metaclass__ = CrazyABCMeta
@always_override
def foo(self):
pass
@always_override_property
def bar(self):
pass
class B(A):
def foo(self):
pass
bar = 1
class C(B):
pass
class D(C):
pass
class E(D):
def foo(self):
pass
@property
def bar(self):
return 6
for cls in (B, E):
cls()
print ("Pass {}".format(cls.__name__))
for cls in (C, D):
try:
print cls()
except TypeError:
print ("Pass {}".format(cls.__name__))
3
一个修改过的版本的 ABCMeta 应该可以解决这个问题。
在这里,我们不是仅仅检查基类中那些 __isabstractmethod__
被设置为 True
的方法,而是可以检查这个方法是否在类的 MRO(方法解析顺序)中。如果在 MRO 中找到了这个方法,但当前类中没有这个方法,那么我们就可以把它添加到 abstracts
这个集合里。
from abc import ABCMeta, abstractmethod
from _weakrefset import WeakSet
class EditedABCMeta(ABCMeta):
def __new__(mcls, name, bases, namespace):
cls = type.__new__(mcls, name, bases, namespace)
# Compute set of abstract method names
abstracts = set(name
for name, value in namespace.items()
if getattr(value, "__isabstractmethod__", False))
for base in cls.__mro__:
for name, value in base.__dict__.items():
if getattr(value, "__isabstractmethod__", False) and name not in cls.__dict__:
abstracts.add(name)
cls.__abstractmethods__ = frozenset(abstracts)
# Set up inheritance registry
cls._abc_registry = WeakSet()
cls._abc_cache = WeakSet()
cls._abc_negative_cache = WeakSet()
cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter
return cls
class A(object):
__metaclass__ = EditedABCMeta
@abstractmethod
def veryspecificmethod(self):
pass
class B(A):
def veryspecificmethod(self):
print 'doing something in B'
@abstractmethod
def foo(self):
print 'foo from B'
class C(B):
def foo(self):
pass
class D(C, B):
pass
if __name__ == '__main__':
for cls in (C, D):
try:
cls().veryspecificmethod
except TypeError as e:
print e.message
print '-'*20
for cls in (C, D):
try:
cls().foo
except TypeError as e:
print e.message
输出:
Can't instantiate abstract class C with abstract methods veryspecificmethod
Can't instantiate abstract class D with abstract methods foo, veryspecificmethod
--------------------
Can't instantiate abstract class C with abstract methods veryspecificmethod
Can't instantiate abstract class D with abstract methods foo, veryspecificmethod
编辑:
添加一个特殊的装饰器 @enforcedmethod
,它可以满足你的需求,而不会影响 @abstractmethod
:
from abc import ABCMeta, abstractmethod
def enforcedmethod(func):
func.__enforcedmethod__ = True
return func
class EditedABCMeta(ABCMeta):
def __call__(cls, *args, **kwargs):
enforcedmethods = set()
for base in cls.__mro__:
for name, value in base.__dict__.items():
if getattr(value, "__enforcedmethod__", False) and name not in cls.__dict__:
enforcedmethods.add(name)
if enforcedmethods:
raise TypeError("Can't instantiate abstract class {} "
"with enforced methods {}".format(
cls.__name__, ', '.join(enforcedmethods)))
else:
return super(EditedABCMeta, cls).__call__(*args, **kwargs)
class A(object):
__metaclass__ = EditedABCMeta
@enforcedmethod
def veryspecificmethod(self):
pass
@abstractmethod
def simplemethod(self):
pass
class B(A):
def veryspecificmethod(self):
print 'doing something in B'
def simplemethod(self):
pass
class C(B):
pass
class D(C):
def veryspecificmethod(self):
print 'doing something in D'
输出:
>>> D().veryspecificmethod()
doing something in D
>>> C().veryspecificmethod()
Traceback (most recent call last):
File "<pyshell#23>", line 1, in <module>
C().veryspecificmethod()
File "C:\Python27\so.py", line 19, in __call__
cls.__name__, ', '.join(enforcedmethods)))
TypeError: Can't instantiate abstract class C with enforced methods veryspecificmethod