Pull to refresh

Comments 24

Класс делает всё: читает, классифицирует, рисует, анализирует. Лучше разделить на:

* DataLoader (читает и декодирует)

* DataAnalyzer (анализирует)

* Plotter (рисует)

Хотя, я видел подобное на scala

Паттерн интерпретатор или что-то такое

// 1. Общий тип для всех операций
trait DataTask

// 2. Case-классы для конкретных задач (хранят данные для операции)
case class PlotRawData(title: String) extends DataTask
case class AnalyzeData(method: String) extends DataTask
case class LoadDataFrom(path: String) extends DataTask

// 3. Список задач (конфигурация)
val pipeline: List[DataTask] = List(
  LoadDataFrom("/path/to/data"),
  PlotRawData(title = "Initial view"),
  AnalyzeData(method = "mean")
)

// 4. "Исполнитель", который разбирает и выполняет задачи
def process(tasks: List[DataTask]): Unit = {
  tasks.foreach { task =>
    task match {
      case PlotRawData(title)   => println(s"Рисую график: $title")
      case AnalyzeData(method)  => println(s"Анализирую методом: $method")
      case LoadDataFrom(path)   => println(s"Загружаю данные из: $path")
    }
  }
}

process(pipeline)

Из класса DataProcessing я как раз и вызываю необходимые методы других классов, которые как раз и выполняют необходимый функционал. Просто для раскрытия темы не было необходимости в демонстрации других классов

А в приведённом Вами примере я не вижу, в каком месте можно «включать» и «выключать» выполнение определённой функции. Если я не прав, то поясните пожалуйста

На эксперименты с максимальной гибкостью вам хватит такого. Создаёте какой угодно скрипт, но с ограничениями для безопасности. И можете хоть с клавиатуры его вводить и отправлять на исполнение.

# Скрипт - это строка, описывающая композицию.
# Функции будут вызваны в момент выполнения.
script = "format_report(process_data(load_data('/my/data')))"

# Реальные функции, которые будут доступны "внутри" eval
def _load_data(path: str): return [1, 2, -5, 10, -3]
def _process_data(data: list): return [x for x in data if x > 0]
def _format_report(data: list): return f"Report: sum = {sum(data)}"

# Исполнитель
def run_script_with_eval(script_string: str):
    # Создаем "окружение" для eval, чтобы он не имел доступа ко всему.
    # Мы явно указываем, какие имена (функции) ему разрешено видеть.
    allowed_context = {
        'load_data': _load_data,
        'process_data': _process_data,
        'format_report': _format_report,
    }

    # eval(source, globals, locals)
    # Передаем наш словарь как "глобальное" окружение для этой команды.
    return eval(script_string, allowed_context)

# Ключевой шаг: передаем словарь, где __builtins__ ПУСТОЙ.
# Это блокирует доступ ко всем встроенным функциям.
secure_globals = {
    "__builtins__": {}, 
    **allowed_context  # Добавляем наши разрешенные функции
}
    

return eval(script_string, secure_globals)

Хороший подход, спасибо

Строчки жалко?

Можете пояснить, что Вы имеете ввиду?

Как это дебажить?

Как по мне, можно добавить дополнительный вывод (в консоль или соответствующий лог, неважно) и в нужных местах точки останова. Должно быть достаточно

Всегда думала, что это классика в питоне, селект из словаря. Но понимаю радость первооткрывателя 😁

Согласен с тем, что селект из словаря - стандартная вещь питона

А вот для такой синхронизации двух словарей я прям сидел и думал, так что решил поделиться сначала с коллегой, а затем и с Хабром. Так что получилась двойная радость первооткрывателя 😁

Если работает, то ок...но чёт мне не нравится, а разбираться лень.. вот self._parameters['Raw_Data'] - с телефона не вижу откуда вообще это взялось.

В общем, то что сейчас кажется элегантным - скорее всего забудется/поломается в самый неприятный момент.

Рекомендую скопировать код и попросить chatGPT написать 3-5 вариантов с обоснованием рекомендованного. Предложить разные паттерны под эту задачу GPT точно может.

При переносе кода из проекта случайно стёр инициализацию self._parameters, спасибо что заметили

А так, это просто поле класса DataProcessing, в котором хранится словарь processing_parama

А для чего там вообще ключ RawData? Не проще ли сразу сделать такой атрибут класса?

Я думал вы просто сделаете словарь вида

Key: [bool, funk]

Потом когда будете считывать данные, то будете менять значения bool, после в чего в цикле выполнять funk если bool

Тоже неплохой вариант, но для этого надо из класса GUI «знать» нужные методы из класса DataProcessing, что является не совсем корректным паттерном, на мой взгляд.

В любом случае необходимо где-то сохранить соответствие функций и чекбоксов. В вашем паттерне это сделано в классе DataProcessing, но можно и наоборот.

Во фронтенде предложенный @brizol паттерн повсеместно применяется в кнопках, то самое <button onclick="myFunction()">Click me</button>

Так а какую "кучу флагов" то удалось заменить? В примере из всего 3. А в целом как-то путано на мой взгляд.

Да, в примере их только 3, но в целом я заменил уже флагов 6 таким образом, хотя ещё далеко не закончил проект

Словари в таких задач действительно удобны. Единственное что появится вопрос сохранения состояний при перезапуске (если оно нужно).

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

 Единственное что появится вопрос сохранения состояний при перезапуске (если оно нужно).

JSONы в помощь)

Это тоже вариант, но конфиг файлы с секциями как по мне понятнее выглядят :)

  • Словари можно типизировать, иначе они рано или поздно разъедутся

from typing import TypedDict, Any

class RawDataPlottingParams(TypedDict):
    plotting_raw_data: bool
    plotting_init_data: bool
    plotting_buffers_data: bool
    kwargs: dict[str, Any]

class ProcessingParameters(TypedDict):
    Raw_Data: RawDataPlottingParams
  • Для RawData возможно уместнее defaultdict со значением по умолчанию false, чтобы при отсутствующем ключе все не падало.

  • Лучше всего использовать dataclass, pydantic-settings или yaml/toml конфиг

Спасибо за полезное замечание, учту

Прочтя о замене флагов словарями, подумал о замене множества очень похожих ветвлений массивы условий и циклы по ним. Оказалось, речь о слое конфига.

Немного не по теме, но хочу поделиться структурой конфига, которую я использовал в одном из проектов по сбору данных. Тоже словари, тоже разделение взаимосвязей, вдруг пригодится. Фидбек тоже приветствуется.

Скрытый текст

Задача - конфигурация платы сбора данных и подключенных к ней датчиков через аналоговые и цифровые порты без изменения кода программы.

Конфигурация IO хранится в JSON файле, как вложенный словарь. Логика инициализации датчиков, обработка и сбор данных осуществляется через класс BoardIO. В коде есть методы, которые принимают DeviceIO и используют подходящию логику чтения/записи в зависимости от типа датчика/терминала.

# Создаем класс с аттрибутами для инициализации датчика или устройства
@dataclass(frozen=True)
class DeviceIO:
    name: str
    ch_type: Literal["analog", "digital"]
    io: Literal["in", "out"]
    terminals: list[int]
    channel: int
    ...

# Создаем класс с устройствами, к которым обращаемся по имени в коде
@dataclass(slots=True)
class BoardIO:
    torque_l: DeviceIO
    torque_r: DeviceIO
    sp_counter: DeviceIO
    encoder_r: DeviceIO
    ...

    # При инициализации загружаем словарь с конфигом из JSON для всех устройств датчика
    def _load_config(self, config_channels: str) -> None:

        with open(config_channels, encoding="utf_8") as file:
            daq_config = json.load(file)

        # Присваиваем аттрибуты из JSON для каждого устройства
        for device, params in daq_config["channels"].items():
            if device in self.__dataclass_fields__:
                new_class = DeviceIO(name=device, **params)
                setattr(self, device, new_class)
            else:
                logger.error(
                    "Naming error in the '%s'. '%s' is not defined"
                    "in the dataclass, so it was not initialised.",
                    config_channels,
                    device,
                )

Я не совсем понимаю аннотацию через dir, у вас создан пользовательский тип?

Sign up to leave a comment.

Articles