目录
- 楔子
- 举个例子
- 注释
- 键值对
- 字符串
- 整数
- 浮点数
- 布尔值
- 日期
- 数组
- 表
- 行内表
- 表数组
楔子
上一篇文章我们介绍了 yaml,虽然 yaml 的表达能力已经很丰富了,但 GitHub 觉得还是不够优雅,所以鼓捣出了一个 toml。toml 有着比 yaml 更简洁的语法,它的目标就是成为一个最简单的配置文件格式。
然后 Python 解析 toml 文件需要使用一个名字也叫 toml 的库,直接 pip install toml 即可。
举个例子
有了 ini 和 yaml,相信 toml 学习来也很简单,先直接看一个例子吧。
import toml | |
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 数字、下划线、短横线。
import toml | |
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'} | |
""" |
如果不是裸键,那么就必须使用引号括起来,但是此时也支持我们使用更加广泛的键名,但除了特殊场景,否则使用裸键是最佳实践。
import toml | |
config = """ | |
"127.0.0.1" = "value" | |
"character encoding" = "value" | |
"ʎǝʞ" = "value" | |
'key2' = "value" | |
'quoted "value"' = "value" | |
""" | |
data = toml.loads(config) | |
print(data) | |
""" | |
{'127.0.0.1': 'value', | |
'character encoding': 'value', | |
'ʎǝʞ': 'value', | |
'key2': 'value', | |
'quoted "value"': 'value'} | |
""" |
注意:裸键不能为空,但空引号键是允许的(虽然不建议如此)。
= "没有键名" # 错误 | |
"" = "空" # 正确但不鼓励 | |
'' = '空' # 正确但不鼓励 |
然后是点分隔键,它是一系列通过点相连的裸键或引号键,这允许我们将相近属性放在一起:
import toml | |
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 |
注意:多次定义同一个键是不行的。
import toml | |
config = """ | |
# name 和 "name" 是等价的 | |
name = "古明地觉" | |
"name" = "古明地恋" | |
""" | |
try: | |
data = toml.loads(config) | |
except toml.decoder.TomlDecodeError as e: | |
print(e) | |
""" | |
Duplicate keys! (line 4 column 1 char 36) | |
""" |
对于点分隔键也是如此,只要一个键还没有被直接定义过,我们就仍可以对它和它下属的键名赋值。
import toml | |
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。然后再来说一个特例:
import toml | |
config = """ | |
3.14 = "pi" | |
"3.14" = "pi" | |
""" | |
data = toml.loads(config) | |
print(data) | |
""" | |
{'3': {'14': 'pi'}, '3.14': 'pi'} | |
""" |
如果键是浮点数,那么需要使用引号括起来,否则会被解释为点分隔键。
看完了键,再来看看值(value),其实对于 toml 来说,值比键要简单的多得多。
字符串
字符串共有四种方式来表示:基础式的,多行基础式的,字面量式的,和多行字面量式的。
1)基础字符串由引号包裹,任何 Unicode 字符都可以使用,除了那些必须转义的。
import toml | |
config = """ | |
str = '我是一个字符串,"你可以把我引起来"' | |
""" | |
data = toml.loads(config) | |
print(data) | |
""" | |
{'str': '我是一个字符串,"你可以把我引起来"'} | |
""" |
2)多行字符串由三个引号包裹,允许换行,注意:紧随开头引号的换行会被去除,其它空白和换行会被原样保留。
import toml | |
config = """ | |
str = ''' | |
玫瑰是红色的 | |
紫罗兰是蓝色的 | |
''' | |
""" | |
data = toml.loads(config) | |
print(data) | |
""" | |
{'str': '玫瑰是红色的\n紫罗兰是蓝色的\n'} | |
""" |
这里的引号可以是双引号、也可以是单引号。
整数
整数是纯数字,正数可以有加号前缀,负数的前缀是减号。
import toml | |
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 |
浮点数
一个浮点数由一个整数部分(遵从与十进制整数值相同的规则)后跟上一个小数部分、或一个指数部分组成。如果小数部分和指数部分兼有,那小数部分必须在指数部分前面。
import toml | |
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 格式的日期。
import toml | |
config = """ | |
dt1 = 2020-01-01T12:33:22+00:00 | |
dt2 = 2020-11-12 12: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(2020, 11, 23)} | |
""" |
数组
语法和 Python 的列表类似:
import toml | |
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]} | |
""" |
数组可以跨行,数组的最后一个值后面可以有终逗号(也称为尾逗号)。
import toml | |
config = """ | |
integers2 = [ | |
1, 2, 3 | |
] | |
integers3 = [ | |
1, | |
2, # 这是可以的 | |
] | |
""" | |
data = toml.loads(config) | |
print(data) | |
""" | |
{'integers2': [1, 2, 3], 'integers3': [1, 2]} | |
""" |
表
表,完全可以把它想象成 ini 的 section。
import toml | |
config = """ | |
# 表名的定义规则与键名相同 | |
# 解析之后得到的大字典中就有 "table-1" 这个 key | |
# 并且其 value 也是一个表,在它下方 | |
# 直至下一个表头或文件结束,都是这个表内部的键值对 | |
[table-1] | |
key1 = "some string" | |
key2 = 123 | |
[table-2] | |
key1 = "another string" | |
key2 = 456 | |
""" | |
data = toml.loads(config) | |
print(data) | |
""" | |
{'table-1': {'key1': 'some string', 'key2': 123}, | |
'table-2': {'key1': 'another string', 'key2': 456}} | |
""" |
但是我们之前也实现过类似于这种结构,没错,就是点分隔符:
import toml | |
config = """ | |
# 所以 other-table-1 和 table-1 是等价的 | |
# other-table-2 和 table-2 是等价的 | |
other-table-1.key1 = "some string" | |
other-table-1.key2 = 123 | |
other-table-2.key1 = "another string" | |
other-table-2.key2 = 456 | |
[table-1] | |
key1 = "some string" | |
key2 = 123 | |
[table-2] | |
key1 = "another string" | |
key2 = 456 | |
""" | |
data = toml.loads(config) | |
print(data) | |
""" | |
{'other-table-1': {'key1': 'some string', 'key2': 123}, | |
'other-table-2': {'key1': 'another string', 'key2': 456}, | |
'table-1': {'key1': 'some string', 'key2': 123}, | |
'table-2': {'key1': 'another string', 'key2': 456}} | |
""" |
不过注意:我们必须要把 other-table-1 和 other-table-2 定义在上面,如果我们定义在下面看看会有什么后果:
import toml | |
config = """ | |
[table-1] | |
key1 = "some string" | |
key2 = 123 | |
[table-2] | |
key1 = "another string" | |
key2 = 456 | |
other-table-1.key1 = "some string" | |
other-table-1.key2 = 123 | |
other-table-2.key1 = "another string" | |
other-table-2.key2 = 456 | |
""" | |
data = toml.loads(config) | |
print(data) | |
""" | |
{ | |
'table-1': {'key1': 'some string', 'key2': 123}, | |
'table-2': {'key1': 'another string', | |
'key2': 456, | |
'other-table-1': {'key1': 'some string', | |
'key2': 123}, | |
'other-table-2': {'key1': 'another string', | |
'key2': 456}} | |
} | |
""" |
估计你已经猜到了,它们被当成了 'table-2' 对应的字典里面的 key 了。此外我们还可以将上面两种方式结合起来:
import toml | |
config = """ | |
# [] 里面的不再是一个普通的键,而是点分隔键 | |
# 另外键名周围的空格会被忽略,但是最好不要有 | |
[dog . "tater.man"] | |
type.name = "哈巴狗" | |
""" | |
data = toml.loads(config) | |
print(data) | |
""" | |
{ | |
'dog': {'tater.man': {'type': {'name': '哈巴狗'}}} | |
} | |
""" |
表的里面也是可以没有键值对的:
import toml | |
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': {}}}}} | |
} | |
} | |
""" |
总的来说还是蛮强大的,但是要注意:不能重复定义。
行内表
行内表提供了一种更为紧凑的语法来表示表,因为上面每一个键值对都需要单独写一行,比如:
[table1] | |
a = 1 | |
b = 2 | |
c = 3 | |
# 最终可以得到 | |
# {'table1': {'a': 1, 'b': 2, 'c': 3}} |
但是除了上面的表达方式之外,我们还可以采用行内表:
import toml | |
config = """ | |
# 和 Python 字典的表示方式略有不同 | |
# 并且也支持多种 key | |
table1 = {a = 1, b = "二", c.a = "3"} | |
table2 = {c."b c".d = "4"} | |
""" | |
data = toml.loads(config) | |
print(data) | |
""" | |
{ | |
'table1': {'a': 1, 'b': '二', 'c': {'a': '3'}}, | |
'table2': {'c': {'b c': {'d': '4'}}} | |
} | |
""" |
表数组
然后来看看数组和表的结合:
import toml | |
config = """ | |
[name1] | |
girl = "古明地觉" | |
[[name2]] | |
girl = "古明地恋" | |
[name3] | |
[[name4]] | |
""" | |
data = toml.loads(config) | |
print(data) | |
""" | |
{'name1': {'girl': '古明地觉'}, | |
'name2': [{'girl': '古明地恋'}], | |
'name3': {}, | |
'name4': [{}]} | |
""" |
当使用 [[]] 的时候,相当于在 [] 的基础上套上一层列表。并且任何对表数组的引用都指向该数组里最近定义的表元素,这允许我们在最近的表内定义子表,甚至子表数组。
我们再举个更复杂的例子:
import toml | |
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': '车前草'}] | |
} | |
] | |
} | |
""" |
很明显这种定义不是很常用,配置文件应该要非常直观才对,但这已经不是很好理解了。