我目前正在为我的应用程序设计一个配置屏幕。大约有 50 个配置参数,我在 CSV 文件中描述了这些参数,并在 Python 中读取。
当前每个参数行的字段为:
csv_row_count
猫名
attr_name
disp_str
单位
数据类型
min_val
max_val
def_val
增量
cur_val
描述
然后,每个参数都会在我的应用程序中绘制为用户界面,供用户交互,如下所示:
csv_row_count
稍后用作循环的 idx
。如果我想添加另一个要配置的参数,我必须使用唯一的参数并将该参数添加到 CSV 的底部。
我目前有一个类,它将所有信息存储到属性中,并且可以在用户更改后存储回 CSV。
现在,我遇到的问题是我需要对用户在配置屏幕中所做的更改使用react(对于每个参数都是唯一的)。
所以我的想法是创建一个 ConfigEventHandler
类,它有一个 Event
用于何时创建参数(在应用程序启动时,将其绘制到 UI例如)和一个在值更改时触发的 Event
(以更改 UI 中的值,并对已更改的特定设置做出相应 react )。
该类如下所示:
from axel import Event
import logging as log
class ConfigEventHandler:
def __init__(self):
# Make an Event for when a config row is created (usually only at boot of application)
self.on_create_ev = Event()
self.on_create_ev += self._on_create_event
# Make an Event for when a config row has changed. Fire-ing of this event will be after a change in the attribute.
self.on_change_ev = Event()
self.on_change_ev += self._on_value_change_event
def _on_create_event(self, idx):
pass
# log.info("Event OnCreate fired for IDX " + str(idx))
def _on_value_change_event(self, idx, user_dict):
log.info("Event On Value Change fired for IDX " + str(idx))
log.info("With user dict: " + str(user_dict))
我在创建类时以及值发生变化时触发这些事件。此事件识别哪个值已更改的唯一方法是通过与 CSV 中所述的 csv_row_count
属性相关的 idx
值。
这意味着,如果我想对每个更改的参数使用react,我需要用大量 if
语句填充 _on_value_change_event
方法,例如:
if idx == 0:
react_to_param0()
elif idx == 1:
react_to_param1()
etc...
我觉得这确实是很糟糕的做法(在任何程序语言中),如果有人搞乱了 CSV 文件中的 idx 编号(例如几个月后的我,或者将接管我的项目的人)。
所以我的问题是,以比大量 if
语句和列表 idx 之间的关系更干净的方式解决此关系问题的一个好的替代方案是什么
和 CSV 索引使其更加面向 future ,并保持代码可读?
我曾想过为每个可配置参数创建一个单独的类实例,但并没有真正看到这会如何打破 csv_row_count
/idx
之间看似“脆弱”的关系code> 以及对一个特定参数的更改使用react的独特函数。
最佳答案
以“Pythonic”方式做事通常涉及使用字典,字典是该语言的主要数据结构之一,已针对速度和最小内存使用量进行了高度优化。
考虑到这一点,我要做的第一件事就是将 CSV 文件中的配置参数合并为一个。由于 CSV 文件的每一行都有一个,因此合乎逻辑的做法是使用 csv.DictReader
读取它们,因为它返回每一行作为值的字典。您可以使用每个字典的唯一 csv_row_count
(又名 idx
)字段作为上层 的键,轻松地构建字典中的字典。字典
.
这就是我的意思:
import csv
from pprint import pprint
CONFIG_FILEPATH = 'parameters.csv'
config_params = {}
with open(CONFIG_FILEPATH, 'r', newline='') as configfile:
reader = csv.DictReader(configfile, escapechar='\\')
csv_row_count = reader.fieldnames[0] # First field is identifier.
for row in reader:
idx = int(row.pop(csv_row_count)) # Remove and convert to integer.
config_params[idx] = row
print('config_params =')
pprint(config_params, sort_dicts=False)
如果您的 CSV 文件包含如下行:
csv_row_count,cat_name,attr_name,disp_str,unit,data_type,min_val,max_val,def_val,incr,cur_val,desc
15,LEDS,led_gen_use_als,Automatic LED Brightness,-,bool,0.0,1.0,TRUE,1.0,TRUE,Do you want activate automatic brightness control for all the LED's?
16,LEDS,led_gen_als_led_modifier,LED Brightness Modifier,-,float,0.1,1.0,1,0.05,1,The modifier for LED brightness. Used as static LED brightness value when 'led_gen_use_als' == false.
17,LEDS,led_gen_launch_show,Enable launch control LEDs,-,bool,0.0,1.0,TRUE,1.0,TRUE,Show when launch control has been activated\, leds will blink when at launch RPM
使用上面的代码读取它会生成以下字典中的字典:
config_params =
{15: {'cat_name': 'LEDS',
'attr_name': 'led_gen_use_als',
'disp_str': 'Automatic LED Brightness',
'unit': '-',
'data_type': 'bool',
'min_val': '0.0',
'max_val': '1.0',
'def_val': 'TRUE',
'incr': '1.0',
'cur_val': 'TRUE',
'desc': 'Do you want activate automatic brightness control for all the '
"LED's?"},
16: {'cat_name': 'LEDS',
'attr_name': 'led_gen_als_led_modifier',
'disp_str': 'LED Brightness Modifier',
'unit': '-',
'data_type': 'float',
'min_val': '0.1',
'max_val': '1.0',
'def_val': '1',
'incr': '0.05',
'cur_val': '1',
'desc': 'The modifier for LED brightness. Used as static LED brightness '
"value when 'led_gen_use_als' == false."},
17: {'cat_name': 'LEDS',
'attr_name': 'led_gen_launch_show',
'disp_str': 'Enable launch control LEDs',
'unit': '-',
'data_type': 'bool',
'min_val': '0.0',
'max_val': '1.0',
'def_val': 'TRUE',
'incr': '1.0',
'cur_val': 'TRUE',
'desc': 'Show when launch control has been activated, leds will blink '
'when at launch RPM'}}
函数的调度
现在,就对每个参数更改使用react而言,一种方法是使用函数装饰器来定义何时根据其第一个参数的值调用它所应用的装饰器的函数。这将消除在 _on_value_change_event()
方法中使用一长串 if
语句的需要 - 因为它可以通过对命名修饰函数的一次调用来替换。
我从文章 Learn About Python Decorators by Writing a Function Dispatcher 中得到了实现装饰器来实现此目的的想法。 ,尽管我使用类(而不是具有嵌套函数的函数)以稍微不同的方式实现了它,我认为这有点“干净”。另请注意装饰器本身如何使用名为 registry
的内部字典来存储有关信息并进行实际调度 - 另一种“Pythonic”方式来执行操作。
再说一次,这就是我的意思:
class dispatch_on_value:
""" Value-dispatch function decorator.
Transforms a function into a value-dispatch function, which can have
different behaviors based on the value of its first argument.
See: http://hackwrite.com/posts/learn-about-python-decorators-by-writing-a-function-dispatcher
"""
def __init__(self, func):
self.func = func
self.registry = {}
def dispatch(self, value):
try:
return self.registry[value]
except KeyError:
return self.func
def register(self, value, func=None):
if func is None:
return lambda f: self.register(value, f)
self.registry[value] = func
return func
def __call__(self, *args, **kw):
return self.dispatch(args[0])(*args, **kw)
@dispatch_on_value
def react_to_param(idx): # Default function when no match.
print(f'In default react_to_param function for idx == {idx}.')
@react_to_param.register(15)
def _(idx):
print('In react_to_param function registered for idx 15')
@react_to_param.register(16)
def _(idx):
print('In react_to_param function registered for idx 16')
@react_to_param.register(17)
def _(idx):
print('In react_to_param function registered for idx 17')
# Test dispatching.
for idx in range(14, 19):
react_to_param(idx) # Only this line would go in your `_on_value_change_event()` method.
这是生成的输出,表明它有效。请注意,没有为每个可能的值注册一个函数,其中调用默认函数。
In default react_to_param function for idx == 14.
In react_to_param function registered for idx 15
In react_to_param function registered for idx 16
In react_to_param function registered for idx 17
In default react_to_param function for idx == 18.
关于在配置屏幕中为每个设置调用不同方法的 Pythonic 方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69375376/