Обеспечение наличия атрибутов, на которые ссылается миксин, в дочернем классе

Я реализовал ниже _get_schema метод обеспечения того, чтобы schema атрибут всегда присутствует на Todos instance, и поэтому может использоваться в миксине.

Можно ли так делать? Насколько мне известно, миксины обычно не реализуют конструкторы.

Есть ли лучший способ убедиться, что кто-то не забыл явно установить schema атрибут в Todos class, тем более что этот атрибут также не унаследован от View?

class Mixin:
    def __init__(self):
        self._get_schema()
        super().__init__()

    def _get_schema(self):
        if not hasattr(self, 'schema'):
            msg = f'No "schema" attribute provided in {self.__class__.__name__} class'
            raise Exception(msg)

class View:
    def __init__(self):
        print('view constructor')

class Todos(SchemaMixin, MethodView):
    schema="todo schema"

todos = Todos()

2 ответа
2

Смысл миксина, по крайней мере, как я всегда понимал, заключается во внедрении методов в другие классы, а не в создании экземпляров самого класса миксина. Так что положив __init__() в миксине действительно кажется странным шаблоном. Конечно, __init__() это просто метод: почему бы не внедрить и этот метод? Достаточно честно, но это все еще не так. И действительно ли это необходимо?

Один из вариантов — ввести метод проверки схемы, а затем вызвать его в соответствующем
__init__():

class SchemaMixin:

    def _check_schema(self):
        ...

class Todos(SchemaMixin):

    def __init__(self):
        ...
        self._check_schema()

Или просто используйте обычные функции и избавьтесь от неловкого дела наследования. Не зная более подробной информации о вашем случае, я бы склонился в этом направлении.

def check_schema(obj):
    ...

class Todos():

    def __init__(self):
        check_schema(self)

В наши дни я почти никогда не использую наследование — обычное или смешанное. У меня нет общей политики против этого, но я все чаще обнаруживаю, что мне это просто не нужно. Если классу что-то нужно, я передаю это в качестве аргумента. Если несколько классов нуждаются в одной и той же операции, я использую функции.

  • Спасибо! Что касается «если классу что-то нужно, я передаю это в качестве аргумента» — выполнимо ли это в данном случае, поскольку Todos — это просто класс, вызываемый структурой при каждом попадании определенного URL-адреса, и я не могу контролировать, какие аргументы передаются его конструктору?

    — Барцевич

Я считаю нормой просто попытаться получить доступ к schema атрибут, и пусть он завершится с ошибкой AttributeError, если его нет. Но есть две возможности поймать его до выполнения (во время компиляции или с помощью средства проверки типов).

С помощью __init_subclass__()

Это может быть хорошим вариантом для добавления __init_subclass__() в класс миксина. Он будет вызываться (во время компиляции) всякий раз, когда класс наследует миксин.

class SchemaMixin:
    def __init_subclass__(cls):
        if not hasattr(cls, 'schema'):
            raise Exception(f"class {cls.__name__} is missing a 'schema' attribute.")

    def do_something_with_the_schema(self, *args):
        # method can rely on self having a 'schema' attribute.
        print(self.schema)
            
class Todo(SchemaMixin):
    schema = "testing"
    
class BadTodo(SchemaMixin):
    pass

Когда Python пытается скомпилировать класс BadTodo, это вызовет следующее исключение (трассировка опущена):

Exception: class BadTodo is missing a 'schema' attribute.

Использование подсказок типа

Если вы используете подсказки типа, это можно проверить с помощью Протоколы PEP 544 для определения структурных подтипов.

from typing import Protocol

class Schema(Protocol):
    schema: ClassVar[str]

Функции / методы, которым нужен тип с атрибутом схемы, будут использовать схему в качестве подсказки типа:

def use_a_schema(x: Schema) -> some_type_hint:
    print(x.schema)

И средство проверки типов, такое как mypy, может проверить, что он вызывается с аргументами, имеющими schema атрибут.

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *