Skip to content Skip to sidebar Skip to footer

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"