dataclass是Python3.7新增的标准库dataclasses的装饰器,可自动生成__init__()
等方法,支持类型提示。
yaml是常用的配置文件格式,语法比json更简洁,支持时间格式,在Python中通过第三方库PyYAML进行读写。
用dataclass组织配置文件信息,比用字典更便于程序的后续开发。
对于Python而言,写程序的本质其实是程序员通过代码指导解释器工作。
配置信息通常是键值对形式,用字典表示很自然,但是有个问题:解释器并不知道字典都有哪些“键”。字典像是一个不提供菜单的饭店,老客户点对菜名才有得吃,相对而言,用类去组织配置信息虽然准备工作多一点,但亮出菜单后,点菜就方便很多。
本文简单介绍读取yaml配置文件,以及如何用dataclass组织配置信息。
有config.yaml
文件内容如下:
w: 70
e: 140
s: 0
n: 60
res: 0.5
time: 2022-09-25 00:00:00
则读取方式为:
import yaml
with open('./config.yaml') as f:
config = yaml.load(f, Loader=yaml.FullLoader)
config
字典为:
{'w': 70,
'e': 140,
's': 0,
'n': 60,
'res': 0.5,
'time': datetime.datetime(2022, 9, 25, 0, 0)}
可以看到,时间直接解析成了datetime类型。
yaml不细讲了,其详细语法以及更多特性可以自行搜索。
config
字典得通过如config['w']
的形式调用各配置项,方括号和引号打起来麻烦不说,要是打错配置名了在IDE也没有任何提示。如果用类重新组织config
内容可以这么做:
class Config(object):
def __init__(self, config):
self.w = config['w']
self.e = config['e']
self.s = config['s']
self.n = config['n']
self.time = config['time']
config = Config(config)
那么接下来就可以通过如config.w
的形式调用各配置项,比字典省去方括号和引号,并且在IDE中按下.
的时候会出现候选,若是打错配置名也会出现提示。
这个类若是用dataclass实现则是这样:
from dataclasses import dataclass
@dataclass
class Config:
w: int
e: int
s: int
n: int
res: float
time: int
config = Config(**config)
效果完全相同但是更简洁了,不用手写__init__()
了。
你可能发现time: int
似乎不太对劲,type(config.time)
返回的仍然是datetime.datetime
,这涉及到Python的类型提示并不实际影响数据类型,因为Python仍是弱类型,这个类型提示是给程序员和IDE看的。
所以time: int
改成time: datetime
会更合理(要事先from datetime import datetime
)。用上合理的类型提示会发生一件事:当你在IDE中输入config.time.
,下拉提示就是包含strftime
等datetime.datetime
类型特有方法。
__post_init__()
dataclass支持一种特殊方法__post_init__()
,会在自动生成的__init__()
被调用中的最后自动被调用,相当于在自动生成的__init__()
上做补充。
from datetime import datetime
from dataclasses import dataclass, field
import numpy as np
@dataclass
class Config:
w: int
e: int
s: int
n: int
res: float
time: datetime
lon: np.ndarray = field(init=False)
lat: np.ndarray = field(init=False)
def __post_init__(self):
w, e, s, n = self.w, self.e, self.s, self.n
res = self.res
assert -180 <= w < e <= 180 and -90 <= s < n <= 90
self.lon = np.linspace(w, e, int((e - w) / res))
self.lat = np.linspace(s, n, int((n - s) / res))
config = Config(**config)
这个例子中,__post_init__()
做了两件事:
w, e, s, n
的合法性检验,保证其是合法经纬度范围。lon: np.ndarray = field(init=False)
表示lon
这个属性不是__init__()
初始化过程中产生的,所以不需要传给自动生成的__init__()
,该例中在__post_init__()
中被赋值。通过装饰器dataclass可以简化多属性类的编写,本文仅介绍了我认为很实用且便于表述的一种应用,实际上dataclass的功能、参数还有很多没有讲,比如默认值、字符串形式、比较方式、给__post_init__()
传参等等,读者可在Python官网上查到详细的文档。
回到dataclass用于配置文件组织这个具体问题上,可拓展的细节还有:
__post_init__()
过程中,但那样自动生成的__init__()
有点脱裤子放屁的感觉,还不如手写普通class,有待研究。