Python解析toml配置文件的方法分享

2022-09-18 10:40:40

目录楔子举个例子注释键值对字符串整数浮点数布尔值日期数组表行内表表数组楔子上一篇文章我们介绍了yaml,虽然yaml的表达能力已经很丰富了,但GitHub觉得还是不够优雅,所以鼓捣出了一个...

目录
楔子
举个例子
注释
键值对
字符串
整数
浮点数
布尔值
日期
数组

行内表
表数组

楔子

上一篇文章我们介绍了 yaml,虽然 yaml 的表达能力已经很丰富了,但 github 觉得还是不够优雅,所以鼓捣出了一个 toml。toml 有着比 yaml 更简洁的语法,它的目标就是成为一个最简单的配置文件格式。

然后 python 解析 toml 文件需要使用一个名字也叫 toml 的库,直接 pip install toml 即可。

举个例子

有了 ini 和 yaml,相信 toml 学习来也很简单,先直接看一个例子吧。

importtoml

config="""
title="toml小栗子"

[owner]
name="古明地觉"
age=17
place="东方地灵殿"
nickname=["小五","少女觉","觉大人"]

[database]
host="127.0.0.1"
port=5432
username="satori"
password="123456"
echo=true

[server]
[server.v1]
api="1.1"
enable=false

[server.v2]
api="1.2"
enable=true

[client]
client=[
["socket","webservice"],
[5555]
]
address=[
"xxxx",
"yyyy"
]
"""

# loads:从字符串加载
# load:从文件加载
# dumps:生成 toml 格式字符串
# dump:生成 toml 格式字符串并写入文件中
data=toml.loads(config)
print(data)
"""
{
'title':'toml小栗子',
'owner':{'name':'古明地觉',
'age':17,
'place':'东方地灵殿',
'nickname':['小五','少女觉','觉大人']},
'database':{'host':'127.0.0.1',
'port':5432,
'username':'satori',
'password':'123456',
'echo':True},
'server':{'v1':{'api':'1.1','enable':False},
'v2':{'api':'1.2','enable':True}},
'client':{'client':[['socket','webservice'],[5555]],
'address':['xxxx','yyyy']}
}
"""

toml 是采用 var = value 的形式进行配置,然后也有类似于 ini 里面的 section,每个 section 都是字典中的一个 key,然后该 key 也对应一个字典。但是我们注意看最开始的 title,由于它上面没有 section,所以它是一个单独的 key。

而且还有一点就是 toml 支持嵌套,我们看到 server.v1,表示 v1 是 server 对应的字典里面的一个 key,然后 v1 对应的值还是一个字典。

toml 变得更加简单了,而且写来也非常像 Python,它有如下特点:

toml 文件是大小写敏感的;

toml 文件必须是有效的 UTF-8 编码的 Unicode 文档;

toml 文件的空白符应该是 Tab 或者空格;

toml 文件的换行是 LF 或者 CRLF;

然后我们来介绍一下 toml 的数据结构。

注释

toml 采用 # 表示注释,举个例子:

#这是注释
key="value"#也是注释

可以解析一下看看会得到什么,剧透:会得到只包含一个键值对的字典。

键值对

TOML 文档最基本的构成区块是键值对,键名在等号的左边、值在右边,并且键名和键值周围的空白会被忽略。此外键、等号和值必须在同一行(不过有些值可以跨多行)。

key="value"

键名可以是裸露的(裸键),引号引起来的(引号键),或点分隔的(点分隔键)。裸键只能包含:ascii 字符、ascii 数字、下划线、短横线。

importtoml

config="""
key="value"
bare_key="value"
bare-key="value"
#1234会被当成字符串
1234="value"
"""

data=toml.loads(config)
print(data)
"""
{'key':'value',
'bare_key':'value',
'bare-key':'value',
'1234':'value'}
"""

如果不是裸键,那么就必须使用引号括起来,但是此时也支持我们使用更加广泛的键名,但除了特殊场景,否则使用裸键是最佳实践。

importtoml

config="""
"127.0.0.1"="value"
"characterencoding"="value"
""="value"
'key2'="value"
'quoted"value"'="value"
"""

data=toml.loads(config)
print(data)
"""
{'127.0.0.1':'value',
'characterencoding':'value',
'':'value',
'key2':'value',
'quoted"value"':'value'}
"""

注意:裸键不能为空,但空引号键是允许的(虽然不建议如此)。

="没有键名"#错误
""="空"#正确但不鼓励
''='空'#正确但不鼓励

然后是点分隔键,它是一系列通过点相连的裸键或引号键,这允许我们将相近属性放在一起:

importtoml

config="""
name="橙子"
physical.color="橙色"
physical.shape="圆形"
site."google.com"=true
site.google.com=true
a.b.c.d=123
"""

data=toml.loads(config)
print(data)
"""
{
'name':'橙子',
'physical':{'color':'橙色',
'shape':'圆形'},
'site':{'google.com':True,
'google':{'com':True}},
'a':{'b':{'c':{'d':123}}}
}
"""

我们看到这个点分隔符不错哟,自动实现了嵌套结构,并且点分隔符周围的空白会被忽略。

fruit.name="香蕉"#这是最佳实践
fruit.color="黄色"#等同于fruit.color
fruit.flavor="香蕉"#等同于fruit.flavor

注意:多次定义同一个键是不行的。

importtoml

config="""
#name和"name"是等价的
name="古明地觉"
"name"="古明地恋"
"""

try:
data=toml.loads(config)
excepttoml.decoder.TomlDecodeErrorase:
print(e)
"""
Duplicatekeys!(line4column1char36)
"""

对于点分隔键也是如此,只要一个键还没有被直接定义过,我们就仍可以对它和它下属的键名赋值。

importtoml

config="""
fruit.apple.smooth=true#此时可以继续操作fruit、fruit.apple,它们都是字典
#给fruit这个字典加一个key
fruit.orange=2
#给fruit.apple加一个key
fruit.apple.color="red"
"""

data=toml.loads(config)
print(data)
"""
{
'fruit':{'apple':{'smooth':True,
'color':'red'},
'orange':2}
}
"""

但下面这个操作是不行的:

#将fruit.apple的值定义为一个整数
fruit.apple=1
#但接下来就不合法了,因为整数不能变成字典
fruit.apple.smooth=true

#如果我们设置fruit.apple={},那么第二个赋值是可以的
#没错,我们可以通过{}直接创建一个字典

可以看到,真的很像 Python。然后再来说一个特例:

importtoml

config="""
3.14="pi"
"3.14"="pi"
"""

data=toml.loads(config)
print(data)
"""
{'3':{'14':'pi'},'3.14':'pi'}
"""

如果键是浮点数,那么需要使用引号括起来,否则会被解释为点分隔键。

看完了键,再来看看值(value),其实对于 toml 来说,值比键要简单的多得多。

字符串

字符串共有四种方式来表示:基础式的,多行基础式的,字面量式的,和多行字面量式的。

1)基础字符串由引号包裹,任何 Unicode 字符都可以使用,除了那些必须转义的。

importtoml

config="""
str='我是一个字符串,"你可以把我引起来"'
"""

data=toml.loads(config)
print(data)
"""
{'str':'我是一个字符串,"你可以把我引起来"'}
"""

2)多行字符串由三个引号包裹,允许换行,注意:紧随开头引号的换行会被去除,其它空白和换行会被原样保留。

importtoml

config="""
str='''
玫瑰是红色的
紫罗兰是蓝色的
'''
"""

data=toml.loads(config)
print(data)
"""
{'str':'玫瑰是红色的\n紫罗兰是蓝色的\n'}
"""

这里的引号可以是双引号、也可以是单引号。

整数

整数是纯数字,正数可以有加号前缀,负数的前缀是减号。

importtoml

config="""
int1=+99
int2=42
int3=0
int4=-17

#对于大数,可以在数字之间用下划线来增强可读性
#每个下划线两侧必须至少有一个数字。
int5=1_000
int6=5_349_221
int7=53_49_221#印度记数体系分组
int8=1_2_3_4_5#无误但不鼓励
"""

data=toml.loads(config)
print(data)
"""
{'int1':99,
'int2':42,
'int3':0,
'int4':-17,
'int5':1000,
'int6':5349221,
'int7':5349221,
'int8':12345}
"""

但是注意:数字不能以零开头,除了 0 本身。当然 -0 与 +0 也是有效的,并等同于无前缀的零。非负整数值也可以用十六进制、八进制或二进制来表示。

#带有`0x`前缀的十六进制,大小写均可
hex1=0xDEADBEEF
hex2=0xdeadbeef
hex3=0xdead_beef

#带有`0o`前缀的八进制
oct1=0o01234567
oct2=0o755#对于表示Unix文件权限很有用

#带有`0b`前缀的二进制
bin1=0b11010110

浮点数

一个浮点数由一个整数部分(遵从与十进制整数值相同的规则)后跟上一个小数部分、或一个指数部分组成。如果小数部分和指数部分兼有,那小数部分必须在指数部分前面。

importtoml

config="""
#小数
flt1=+1.0
flt2=3.1415
flt3=-0.01

#指数
flt4=5e+22
flt5=1e06
flt6=-2E-2

flt7=6.626e-34
"""

data=toml.loads(config)
print(data)
"""
{'flt1':1.0,
'flt2':3.1415,
'flt3':-0.01,
'flt4':5e+22,
'flt5':1000000.0,
'flt6':-0.02,
'flt7':6.626e-34}
"""

小数部分是一个小数点后跟一个或多个数字,一个指数部分是一个 E(大小写均可)后跟一个整数部分(遵从与十进制整数值相同的规则,但可以包含前导零)。小数点,如果有用到的话,每侧必须紧邻至少一个数字。

#非法的浮点数
invalid_float_1=.7
invalid_float_2=7.
invalid_float_3=3.e+20

与整数相似,可以使用下划线来增强可读性,每个下划线必须被至少一个数字围绕。

flt8=224_617.445_991_228

浮点数值 -0.0 与 +0.0 是有效的,并且应当遵从 IEEE 754。特殊浮点值也能够表示:

#无穷
sf1=inf#正无穷
sf2=+inf#正无穷
sf3=-inf#负无穷

#非数
sf4=nan#是对应信号非数码还是静默非数码,取决于实现
sf5=+nan#等同于`nan`
sf6=-nan#正确,实际码取决于实现

布尔值

布尔值就是惯用的那样,但要小写。

bool1=true
bool2=false

日期

可以是普通的 datetime,或者是遵循 ISO-8859-1 格式的日期。

importtoml

config="""
dt1=2020-01-01T12:33:22+00:00
dt2=2020-11-1212:11:33
dt3=2020-11-23
"""

data=toml.loads(config)
print(data)
"""
{'dt1':datetime.datetime(2020,1,1,12,33,22,tzinfo=...),
'dt2':datetime.datetime(2020,11,12,12,11,33),
'dt3':datetime.date(2020http://www.cppcns.com,11,23)}
"""

数组

语法和 Python 的列表类似:

importtoml

config="""
#每个数组里面的元素类型要一致
integers=[1,2,3]
colors=["红","黄","绿"]
nested_array_of_ints=[[1,2],[3,4,5]]
nested_mixed_array=[[1,2],["a","b","c"]]
numbers=[0.1,0.2,0.5]
"""

data=toml.loads(config)
print(data)
"""
{'colors':['红','黄','绿'],
'integers':[1,2,3],
'nested_array_of_ints':[[1,2],[3,4,5]],
'nested_mixed_array':[[1,2],['a','b','c']],
'numbers':[0.1,0.2,0.5]}
"""

数组可以跨行,数组的最后一个值后面可以有终逗号(也称为尾逗号)。

importtoml

config="""
integers2=[
1,2,3
]

integers3=[
1,
2,#这是可以的
]
"""

data=toml.loads(config)
print(data)
"""
{'integers2':[1,2,3],'integers3':[1,2]}
"""

表,完全可以把它想象成 ini 的 section。

importtoml

config="""
#表名的定义规则与键名相同
#解析之后得到的大字典中就有"table-1"这个key
#并且其value也是一个表,在它下方
#直至下一个表头或文件结束,都是这个表内部的键值对
[table-1]
key1="somestring"
key2=123

[table-2]
key1="anotherstring"
key2=456
"""

data=toml.loads(config)
print(data)
"""
{'table-1':{'key1':'somestring','key2':123},
'table-2':{'key1':'anotherstring','key2':456}}
"""

但是我们之前也实现过类似于这种结构,没错,就是点分隔符:

importtoml

config="""
#所以other-table-1和table-1是等价的
#other-table-2和table-2是等价的
other-table-1.key1="somestring"
other-table-1.key2=123

other-table-2.key1="anotherstring"
other-table-2.key2=456

[table-1]
key1="somestring"
key2=123

[table-2]
key1="anotherstring"
key2=456
"""

data=toml.loads(config)
print(data)
"""
{'other-table-1':{'key1':'somestring','key2':123},
'other-table-2':{'key1':'anotherstring','key2':456},
'table-1':{'key1':'somestring','key2':123},
'table-2':{'key1':'anotherstring','key2':456}}
"""

不过注意:我们必须要把 other-table-1 和 other-table-2 定义在上面,如果我们定义在下面看看会有什么后果:

importtoml

config="""
[table-1]
key1="somestring"
key2=123

[table-2]
key1="anotherstring"
key2=456

other-table-1.key1="somestring"
other-table-1.key2=123

other-table-2.key1="anotherstring"
other-table-2.key2=456
"""

data=toml.loads(config)
print(data)
"""
{
'table-1':{'key1':'somestring','key2':123},
'table-2':{'key1':'anotherstring',
'key2':456,
'other-table-1':{'key1':'somestring',
'key2':123},
'other-table-2':{'key1':'anotherstring',
'key2':456}}
}
"""

估计你已经猜到了,它们被当成了 'table-2' 对应的字典里面的 key 了。此外我们还可以将上面两种方式结合起来:

importtoml

config="""
#[]里面的不再是一个普通的键,而是点分隔键
#另外键名周围的空格会被忽略,但是最好不要有
[dog."tater.man"]
type.name="哈巴狗"
"""

data=toml.loads(config)
print(data)
"""
{
'dog':{'tater.man':{'type':{'name':'哈巴狗'}}}
}
"""

表的里面也是可以没有键值对的:

importtoml

config="""
[x.y.z.w.a.n]

[x.m]

[x.n]

[x]
a.b.c="xxx"
"""

data=toml.loads(config)
print(data)
"""
{'x':
{
'a':{'b':{'c':'xxx'}},
'm':{},
'n':{},
'y':{'z':{'w':{'a':{'n':{}}}}}
}
}
"""

总的来说还是蛮强大的,但是要注意:不能重复定义。

行内表

行内表提javascript供了一种更为紧凑的语法来表示表,因为上面每一个键值对都需要单独写一行,比如:

[table1]
a=1
b=2
c=3
#最终可以得到
#{'table1':{'a':1,'b':2,'c':3}}

但是除了上面的表达方式之外,我们还可以采用行内表:

importtoml

config="""
#和Python字典的表示方式略有不同
#并且也支持多种key
table1={a=1,b="二",c.a="3"}
table2={c."bc".d="4"}
"""

data=toml.loads(config)
print(data)
"""
{
'table1':{'a':1,'b':'二','c':{'a':'3'}},
'table2':{'c':{'bc':{'d':'4'}}}
}
"""

表数组

然后来看看数组和表的结合:

importtoml

config="""
[name1]
girl="古明地觉"

[[name2]]
girl="古明地恋"

[name3]
[[name4]]
"""

data=toml.loads(config)
print(data)
"""
{'name1':{'girl':'古明地觉'},
'name2':[{'girl':'古明地恋'}],
'name3':{},
'name4':[{}]}
"""

当使用 [[]] 的时候,相当于在 [] 的基础上套上一层列表。并且任何对表数组的引用都指向该数组javascript里最近定义的表元素,这允许我们在最近的表内定义子表,甚至子表数组。

我们再举个更复杂的例子:

importtoml

config="""
[[fruits]]
name="苹果"

#会操作[]里面最近定义的{}
[fruits.physical]
color="红色"
shape="圆形"

[[fruits.varieties]]#嵌套表数组
name="蛇果"

[[fruits.varieties]]
name="澳洲青苹"

[[fruits]]
name="香蕉"

[[fruits.varieties]]
name="车前草"
"""

data=toml.loads(config)
print(data)
"""
{
'fruits':
[
{
'name':'苹果',
'physical':{'color':'红色',
'shape':'圆形'},
'varieties':[{'name':'蛇果'},
{'name':'澳洲青苹'}]
},
{
'name':'香蕉',
'varieties':[{'name':'车前草'}]
}
]
}
"""

很明显这种定义不是很常用,配置文件应该要非常直观才对,但这已经不是很好理解了。

以上就是Python解析toml配置文件的方法分享的详细内容,更多关于Python解析toml的资料请关注我们其它相关文章!