首頁 > 軟體

Python解析toml組態檔的方法分享

2022-09-18 22:00:44

楔子

上一篇文章我們介紹了 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': '車前草'}]
            }
        ]
}
"""

很明顯這種定義不是很常用,組態檔應該要非常直觀才對,但這已經不是很好理解了。

以上就是Python解析toml組態檔的方法分享的詳細內容,更多關於Python解析toml的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com