Запретить добавление новых методов в дочерний класс Python - PullRequest
4 голосов
/ 20 апреля 2020

У меня есть два класса, которые должны реализовывать одни и те же тестовые случаи для двух независимых библиотек (назовем их LibA и LibB ). До сих пор я определяю тестовые методы, которые должны быть реализованы в абстрактном базовом классе, который гарантирует, что оба тестовых класса реализуют все желаемые тесты:

from abc import ABC, abstractmethod

class MyTests(ABC):
    @abstractmethod
    def test_foo(self):
        pass

class TestsA(MyTests):
    def test_foo(self):
        pass

class TestsB(MyTests):
    def test_foo(self):
        pass

Это работает, как и ожидалось, но все же может случиться так, что кто-то работает над LibB случайно добавляет test_bar() метод к TestB вместо базового класса. В этом случае пропущенный test_bar() в классе TestA будет go незамеченным.

Есть ли способ запретить добавление новых методов в (абстрактный) базовый класс? Цель состоит в том, чтобы заставить базовые классы добавлять новые методы и, таким образом, внедрять новые методы во все производные классы.

1 Ответ

5 голосов
/ 20 апреля 2020

Да. Это можно сделать с помощью метакласса или начиная с Python 3.6 и далее, с помощью проверки __init_subclass__ базового класса.

__init_sublass__ - это специальный метод, вызываемый языком каждый раз, когда создается экземпляр подкласса. , Таким образом, он может проверить, есть ли у нового класса какой-либо метод, которого нет ни в одном из суперклассов, и вызвать TypeError, когда подкласс объявлен. (__init_subclass__ преобразуется в метод класса автоматически)

class Base(ABC):
    ...
    def __init_subclass__(cls, *args, **kw):
        super().__init_subclass__(*args, **kw)
        # By inspecting `cls.__dict__` we pick all methods declared directly on the class
        for name, attr in cls.__dict__.items():
            attr = getattr(cls, name)
            if not callable(attr):
                continue
            for superclass in cls.__mro__[1:]:
                if name in dir(superclass):
                    break
            else:
                # method not found in superclasses:
                raise TypeError(f"Method {name} defined in {cls.__name__}  does not exist in superclasses")


Обратите внимание, что в отличие от TypeError, вызываемого не реализованными абстрактными методами, эта ошибка возникает во время объявления класса, а не во время создания класса. Если более поздний вариант желателен, вы должны использовать метакласс и перенести проверку в его метод __call__ - однако это усложняет ситуацию, как если бы один метод создавался в промежуточном классе, который никогда не создавался, он не будет вызываться, когда метод доступен в листовом подклассе. Я думаю, что вам нужно больше по приведенному выше коду.

...