Все потоки
Поиск
Написать публикацию
Обновить

Комментарии 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, у вас создан пользовательский тип?

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации