Как формализовать повторяющиеся отношения между непересекающимися группами классов в Python?

У меня есть код Python, который имеет следующую форму:

from dataclasses import dataclass


@dataclass
class Foo_Data:
    foo: int


class Foo_Processor:
    def process(self, data: Foo_Data): ...


class Foo_Loader:
    def load(self, file_path: str) -> Foo_Data: ...


@dataclass
class Bar_Data:
    bar: str


class Bar_Processor:
    def process(self, data: Bar_Data): ...


class Bar_Loader:
    def load(self, file_path: str) -> Bar_Data: ...

У меня есть несколько экземпляров такой настройки данных/процессора/загрузчика, и все классы имеют одинаковые сигнатуры методов по модулю конкретного семейства классов (Foo, Bar и т. д.). Существует ли питонический способ формализовать эти отношения между классами, чтобы обеспечить аналогичную структуру, если я решу создать семейство классов Spam_Data, Spam_Processor и Spam_Loader? Например, я хочу, чтобы что-то Spam_Processor имело метод process, который принимает аргумент типа Spam_Data. Есть ли способ каким-то образом добиться этой стандартизации с помощью абстрактных классов, универсальных типов или какой-либо другой структуры?

Я пробовал использовать абстрактные классы, но mypy правильно указывает, что наличие всех классов *_Data подклассов абстрактного Data класса, а также то, что все классы *_Processor являются подклассами абстрактного Processor класса, нарушает принцип подстановки Лискова, поскольку каждый процессор предназначен только для соответствующего класса данных (т. е. Foo_Processor не может обрабатывать Bar_Data, но можно было бы ожидать, что это возможно, если у этих классов есть суперклассы Processor и Data, которые совместимы таким образом).

Можете ли вы объединить каждый класс _Processor, _Loader и _Data? Вы можете использовать абстрактный базовый класс для определения отношений. Что-то вроде myclass как ABC с load и process как абстрактными методами, затем class Foo(myclass) и определите там конкретные данные, методы загрузки и обработки.

Paul Wilson 31.08.2024 13:19

Почему, например, Foo_Processor вообще является классом, а не простой process_foo функцией?

chepner 31.08.2024 14:54

Я упростил код, чтобы попытаться прояснить мой вопрос и облегчить его поиск/понимание другим, оказавшимся в аналогичной ситуации. @PaulWilson, @chepner, я не включил другой код/файлы, которые помогли бы показать, что другой код может создавать экземпляры Foo_Data, а также Foo_Processor на самом деле имеет атрибуты, которые устанавливаются, а затем используются для обработки. Вот почему я решил сохранить все это отдельно и как классы, а не как функции.

Alex Duchnowski 31.08.2024 18:01
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
3
58
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы можете использовать абстрактные базовые классы (ABC) с дженериками. Таким образом вы можете определить общий интерфейс, гарантируя при этом безопасность типов:

from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Generic, TypeVar

# generic type variable for Data
T = TypeVar('T', bound='BaseData')


@dataclass
class BaseData(ABC):
    pass


class BaseProcessor(ABC, Generic[T]):
    @abstractmethod
    def process(self, data: T) -> None:
        pass


class BaseLoader(ABC, Generic[T]):
    @abstractmethod
    def load(self, file_path: str) -> T:
        pass

Теперь вы можете определить свои конкретные классы

@dataclass
class Foo_Data(BaseData):
    foo: int


class Foo_Processor(BaseProcessor[Foo_Data]):
    def process(self, data: Foo_Data) -> None: ...


class Foo_Loader(BaseLoader[Foo_Data]):
    def load(self, file_path: str) -> Foo_Data: ...


@dataclass
class Bar_Data(BaseData):
    bar: str


class Bar_Processor(BaseProcessor[Bar_Data]):
    def process(self, data: Bar_Data) -> None: ...


class Bar_Loader(BaseLoader[Bar_Data]):
    def load(self, file_path: str) -> Bar_Data: ...

Написание кода таким образом сочетает в себе преимущества общего интерфейса с безопасностью типов.

  • ABC гарантирует, что подклассы реализуют необходимые методы, обеспечивая согласованную структуру.

  • Обобщенные шаблоны позволяют выполнять операции с конкретным типом, улучшая читаемость и удобство обслуживания кода.

В качестве подтверждения с mypy:

mypy script.py
Success: no issues found in 1 source file

Другие вопросы по теме