absctractmethods: как сделать код неудачным при определении класса, а не при создании экземпляра? - PullRequest
0 голосов
/ 05 июля 2019

Что мне не нравится в @absctractmethod, так это то, что он выдает ошибку только при создании экземпляра.Например, это не приведет к ошибке:

from abc import abstractmethod, ABC


class AbstractClass(ABC):

    @abstractmethod
    def func(self):
        pass


class RealClass(AbstractClass):
    pass

произойдет сбой только в том случае, если я создам экземпляр:

r = RealClass()

Я хочу повторно реализовать этот механизм, но он не работает при определении класса, не экземпляр.Для этого я создал метакласс:

class ABCEarlyFailMeta(type):

    direct_inheritors = {}

    def __new__(cls, clsname, bases, clsdict):

        klass = super().__new__(cls, clsname, bases, clsdict)

        class_path = clsdict['__module__'] + '.' + clsdict['__qualname__']

        if bases == ():
            # we get here when we create base abstract class.
            # The registry will later be filled with abstract methods
            cls.direct_inheritors[class_path] = {}

            for name, value in clsdict.items():
                # adding abstract methods on the proper base abstract class
                if getattr(value, '__isabstractmethod__', None) is True:
                    cls.direct_inheritors[class_path][name] = signature(value)

        else:
            # we get here when create inheritors.
            # Here, we need to extract list of abstractmethods
            base_class = bases[0].__module__ + '.' + bases[0].__qualname__
            abstract_method_names = cls.direct_inheritors[base_class]

            # here, we compare current list of methods
            # with list of abstractmethods and fail if some methods are missing
            cls_dictkeys = set(clsdict.keys())

            missing_methods = set(abstract_method_names) - cls_dictkeys
            if missing_methods:
                raise Exception(
                    f'{clsname} must implement methods: {missing_methods}'
                )

        return klass

, когда класс будет создан, не будет создан экземпляр:

class ABCEarlyFail(metaclass=ABCEarlyFailMeta):

    @abstractmethod
    def func(self):
        pass


class Child(ABCEarlyFail):
    pass

>>> Exception: Child must implement methods: {'func'}

У меня вопрос, как найти правильный базовый класс в bases?В этом примере я ищу bases[0], но он потерпит неудачу, если у класса-наследника есть миксин:

class Child(SomeMixin, ABCEarlyFail):
    pass

Итак, что же лучше?

Или, может быть, яизобретать велосипед?

...