Force Implementation Of A Method In All Inheriting Classes
I have a situation in which I want to enforce each and every class inheriting from a certain (abstract) class to implement a method. This is something I would normally achieve usin
Solution 1:
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
classEditedABCMeta(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()
ifgetattr(value, "__isabstractmethod__", False))
for base in cls.__mro__:
for name, value in base.__dict__.items():
ifgetattr(value, "__isabstractmethod__", False) and name notin 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
classA(object):
__metaclass__ = EditedABCMeta
@abstractmethoddefveryspecificmethod(self):
passclassB(A):
defveryspecificmethod(self):
print'doing something in B' @abstractmethoddeffoo(self):
print'foo from B'classC(B):
deffoo(self):
passclassD(C, B):
passif __name__ == '__main__':
for cls in (C, D):
try:
cls().veryspecificmethod
except TypeError as e:
print e.message
print'-'*20for 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
defenforcedmethod(func):
func.__enforcedmethod__ = Truereturn func
classEditedABCMeta(ABCMeta):
def__call__(cls, *args, **kwargs):
enforcedmethods = set()
for base in cls.__mro__:
for name, value in base.__dict__.items():
ifgetattr(value, "__enforcedmethod__", False) and name notin cls.__dict__:
enforcedmethods.add(name)
if enforcedmethods:
raise TypeError("Can't instantiate abstract class {} ""with enforced methods {}".format(
cls.__name__, ', '.join(enforcedmethods)))
else:
returnsuper(EditedABCMeta, cls).__call__(*args, **kwargs)
classA(object):
__metaclass__ = EditedABCMeta
@enforcedmethoddefveryspecificmethod(self):
pass @abstractmethoddefsimplemethod(self):
passclassB(A):
defveryspecificmethod(self):
print'doing something in B'defsimplemethod(self):
passclassC(B):
passclassD(C):
defveryspecificmethod(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
Solution 2:
I'm pretty sure that this isn't a great idea, but I think that you can do this. Checking out the ABCMeta
implementation for inspiration:
from abc import ABCMeta
defalways_override(func):
func._always_override = Truereturn func
classalways_override_property(property):
_always_override = TrueclassCrazyABCMeta(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 classesfor 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 definesfor 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))
returnsuper(CrazyABCMeta, cls).__call__(*args, **kwargs)
# # # # # # # # # # ## TESTS!# # # # # # # # # # #classA(object):
__metaclass__ = CrazyABCMeta
@always_overridedeffoo(self):
pass @always_override_propertydefbar(self):
passclassB(A):
deffoo(self):
pass
bar = 1classC(B):
passclassD(C):
passclassE(D):
deffoo(self):
pass @propertydefbar(self):
return6for 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__))
Post a Comment for "Force Implementation Of A Method In All Inheriting Classes"