Я реализовал ниже _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 ответа
Смысл миксина, по крайней мере, как я всегда понимал, заключается во внедрении методов в другие классы, а не в создании экземпляров самого класса миксина. Так что положив __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)
В наши дни я почти никогда не использую наследование — обычное или смешанное. У меня нет общей политики против этого, но я все чаще обнаруживаю, что мне это просто не нужно. Если классу что-то нужно, я передаю это в качестве аргумента. Если несколько классов нуждаются в одной и той же операции, я использую функции.
Я считаю нормой просто попытаться получить доступ к 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 атрибут.

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