A modified version of ABCMeta should do the trick.
Here instead of checking for methods with __isabstractmethod__
set to True
only in base classes we can check for this is in class's MRO, and if it is found in any of the class in MRO and it is not present in current class then we can add this to the set 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
Output:
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
EDIT:
Adding a special decorator @enforcedmethod
that can meet your requirements without affecting @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'
Output:
>>> 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
B
'svery_specific_method
doesn't call the base class. – Questioning'very_specific_method' in vars(C)
. I don't know much about metaclasses but using them you can check for that during the class creation. – Equitant