Я создаю приложение с GUI для сбора и обработки данных с микроконтроллера на Python с помощью PyQt. И вот я наконец-то доделал часть функционала, предназначенного для взаимодействия компьютера с платой STM32, теперь необходимо было сделать интерфейс для обработки данных, в котором легко можно было бы настраивать параметры выполнения программы. Я начал думать, как не вносить в программу кучу флагов с соответствующими if-else конструкциями, и вот, что я придумал.

Ещё немного контекста
Если кратко, то моя программа строит перемещение железнодорожной телеги с платой по данным гироскопа и акселерометра, этакая автономная система навигации. Отдельно собираются данные, затем запускается их обработка. В результате, пользователь получает необходимые графики и файлы с данными в удобном виде. Нюанс заключался в том, что я хотел предоставить пользователю возможность самому решать, какие результаты ему нужны, а какие нет. Например, в один раз пользователю необходимы и графики исходных данных, и графики отфильтрованных данных с помощью фильтра Калмана (теория алгоритма хорошо описана тут, а практическое применение тут). А в другой раз ему не нужны графики исходных данных. Для этого он активирует или деактивирует соответствующий checkBox в GUI. И вот как это реализовал.

Реализация
Для начала нужно состояния всех чекбоксов сохранить в один словарь любым способом. Получаем такой словарь:
processing_params = {
'Raw_Data': {
'plotting_init_data': True,
'plotting_buffers_data': False,
'plotting_raw_data': True,
'kwargs': {}
}
}
Во вложенный словарь 'kwargs' можно записать необходимые параметры, которые будут принимать методы. В данном случае, оставим его пустым.
Всю последовательность шагов обработки данных я реализовал в отдельном классе DataProcessing, в котором содержатся как и методы обработки данных, так и необходимые поля, в которых хранятся необходимые данные.
Вот основные методы для визуализации собранных данных:
def _plotting_init_data(self):
print('Построение графиков данных выставки из файла ...')
...
def _plotting_buffers_data(self):
print('Построение графиков буферных данных из файла ...')
...
def _plotting_raw_data(self):
print('Построение графиков исходных данных из файла ...')
...
А теперь вот моё ноухау (ну или очередной велосипед). В классе DataProsessing мы создаём второй словарь, ключи в котором полностью совпадают с ключами первого словаря, а в значения записываем сами методы, отвечающие за тот или иной функционал. Продолжая пример, создаём такой словарь:
self._config = {
'Raw_Data': {
'plotting_raw_data': self._plotting_raw_data,
'plotting_init_data': self._plotting_init_data,
'plotting_buffers_data': self._plotting_buffers_data
}
}
И теперь мы можем запустить выполнение всех выбранных функций одним циклом!
# -------------------------------
# Визуализация исходных данных
# -------------------------------
def _raw_data_plotting(self):
print('# -------------------------------')
print('# Визуализация исходных данных')
print('# -------------------------------')
class_params: dir = self._config['Raw_Data']
user_params: dir = self._parameters['Raw_Data']
for key, value in user_params.items():
if value:
class_params[key](**user_params['kwargs'])
А благодаря тому, что Python позволяет передавать в функции неопределённое количество именованных переменных, функции могут иметь абсолютно разные входные параметры (что лучше избегать в данном контексте, но возможность такая есть).
В итоге, получаем только необходимые результаты выполнения и такой вывод в консоль:
# -------------------------------
# Визуализация исходных данных
# -------------------------------
Построение графиков данных выставки из файла ...
Построение данных из файла ...
Представленный код класса DataProsessing целиком:
class DataProcessing:
def __init__(self, parameters: dict):
self._received_data = {}
self._parameters = parameters
self._config = {
'Raw_Data': {
'plotting_raw_data': self._plotting_raw_data,
'plotting_init_data': self._plotting_init_data,
'plotting_buffers_data': self._plotting_buffers_data
}
}
# ---------------------------------
# Основная функция обработки данных
# ---------------------------------
def start(self):
self._decoding()
self._file_classification()
self._raw_data_plotting()
self._raw_data_analysis()
# -------------------------------
# Чтение данных из файлов
# -------------------------------
def _decoding(self):
...
# -------------------------------
# Классификация файлов
# -------------------------------
def _file_classification(self):
...
# -------------------------------
# Визуализация исходных данных
# -------------------------------
def _raw_data_plotting(self):
print('# -------------------------------')
print('# Визуализация исходных данных')
print('# -------------------------------')
class_params: dir = self._config['Raw_Data']
user_params: dir = self._parameters['Raw_Data']
for key, value in user_params.items():
if value:
class_params[key](**user_params['kwargs'])
def _plotting_init_data(self):
print('Построение графиков данных выставки из файла ...')
...
def _plotting_buffers_data(self):
print('Построение графиков буферных данных из файла ...')
...
def _plotting_raw_data(self):
print('Построение графиков исходных данных из файла ...')
...
if __name__ == "__main__":
processing_params = {
'Raw_Data': {
'plotting_init_data': True,
'plotting_buffers_data': False,
'plotting_raw_data': True,
'kwargs': {}
}
}
data_processing = DataProcessing(processing_params)
data_processing.start()
Заключение
Благодаря такой реализации, программу легко дополнять необходимым функционалом. Пользователю необходимо нажать буквально пару кнопок для детальной настройки результатов работы программы, а Вам, как разработчику, добавить несколько полей в нужные словари.
Возможно, есть более элегантный метод решения подобной задачи. Возможно, я сделал очередной велосипед. Но может быть, данная статья поможет Вам в своих проектах.
Всем добра.