应用程序经常要读取配置文件,还要验证输入是否有效、没有配置时使用默认值,于是我就想怎么简化这一流程
文件格式
首先为了方便用户直接改写,文件格式一定要是可读的。Python 标准库里可以读写可读配置的库有 configparser(ini文件)、json、xml,其中 ini、xml 文件不分类型,读取到的都是字符串,所以选择了 json。不过 JSON 有个缺点就是不支持注释,可以忽略这个缺点,提供好文档就行了
数据验证、默认值
我找到了一个 Python 库:trafaret,它可以验证任何形式的数据并转换成需要的格式
这是把一个 dict
转换成 datetime
的例子:
import datetime
import trafaret as t
date = t.Dict({
'year': t.Int,
'month': t.Int,
'day': t.Int
}) >> (lambda d: datetime.datetime(**d))
assert date.check({
'year': 2012, 'month': 1, 'day': 12}) == datetime.datetime(2012, 1, 12)
首先声明个 Dict
结构,指定 key 和值的类型, >>
操作符可以指定自定义的转换函数,转换函数中可以抛出 DataError
错误, date.check()
可以返回验证并转换后的对象
Dict
的 key 除了字符串,还可以指定 Key
类型,Key
可以指定默认值以及把原名字转换成另一个名字:
>>> c = t.Dict({t.Key('un', 'default_user_name', True) >> 'user_name': t.String})
>>> c.check({
'un': 'Adam'})
{
'user_name': 'Adam'}
>>> c.check({})
{
'user_name': 'default_user_name'}
这是声明一个可选的 key 叫 'un'
,默认值是 'default_user_name'
,>>
操作符指定读取后名字转换成 'user_name'
,也可以在参数里指定转换的名字
实现 Config 类
trafaret 转换后得到的是 dict
,dict
用的是字符串类型的 key,这对编辑器不友好,写代码时没有自动补全,而且想改名时也没办法自动批量修改,所以我打算用访问属性的方式访问配置(用 .
操作符访问)
受到各种 ORM 框架的启发,我打算在类属性里声明配置的字段,写法如下:
class NestedConfig(config):
pass
class MyConfig(Config):
bool_field = {OptionalKey(True): t.Bool}
int_field = {OptionalKey(123): t.Int}
nested_config = {OptionalKey({}): NestedConfig}
OptionalKey
这其实就是指定了一些默认参数的 Key 类,这样我们在参数里就只用写默认值了,转换后的名字由类属性的名字指定
class OptionalKey(t.Key):
def __init__(self, default, name=None):
super().__init__(name, default, True)
将声明的字段转换成 Dict
用 Python 的元类可以很容易实现这一点,我们在创建类时扫描声明的字段,转换成 Dict
并保存在 __struct__
类属性里。这里还实现了 Config
的继承,只要在创建 __struct__
的时候把基类已经创建好的 __struct__
合并进去就行了,注意基类不能覆盖子类的字段