Python 解析 ini 配置文件

楔子

在開發過程中,配置文件是少不了的,只不過我們有時會將 py 文件作爲配置文件(config.py),然後在其它的模塊中直接導入。這樣做是一個好主意,不過配置文件是有專門的格式的,比如:ini, yaml, toml 等等。

而對於 Python 而言,也都有相應的庫來解析相應格式的文件,下面我們來看看 ini 文件要如何解析。

ini 文件

先來了解一下 ini 文件的格式:

[satori]
name = 古明地覺
age = 16
where = 東方地靈殿

[koishi]
name = 古明地戀
age = 15
where = 東方地靈殿

[marisa]
name = 霧雨魔理沙
age = 17
where = 魔法森林

; 以分號或井號開頭表示註釋,不影響

ini 文件總分可以分爲三塊,分別是:

可以看到結構還是比較清晰的,那麼 Python 要如何解析呢?Python 解析 ini 文件需要使用一個名叫 configparser 的庫,這個庫是自帶的,我們可以直接用。

import configparser

# 實例化一個 ConfigParser 實例
config = configparser.ConfigParser()
# 打開 ini 文件
config.read("cfg.ini"encoding="utf-8")

# 獲取所有的 section
print(config.sections())
"""
['satori', 'koishi', 'marisa']
"""

# 獲取某一個 section 的所有 parameter
print(config["satori"])
"""
<Section: satori>
"""

# 我們可以像操作字典一樣操作 parameter
print(list(config["satori"]))
"""
['name', 'age', 'where']
"""
print(list(config["satori"].values()))
"""
['古明地覺', '16', '東方地靈殿']
"""
print(list(config["satori"].items()))
"""
[('name', '古明地覺'), ('age', '16'), ('where', '東方地靈殿')]
"""
# 獲取某個 key 對應的 value
# 如果 key 不存在則拋出 KeyError
print(config["marisa"]["where"])
"""
魔法森林
"""
# 也可以調用 get 方法
# 在 key 不存在時,指定一個默認值
print(config["marisa"].get("age"))
"""
17
"""
# 我們發現 age 居然是一個字符串
# 因爲默認解析得到的都是字符串
print(config["marisa"]["age"].__class__)
"""
<class 'str'>
"""

# 可以通過 getint 獲取
# 會將 value 轉成整型,但轉化失敗的話會報錯
# 除了 getint 之外,還有 getfloat、getboolean
print(config["marisa"].getint("age") == 17)
"""
True
"""

# 最後也可以直接轉成字典
print(dict(config["koishi"]))
"""
{'name': '古明地戀', 'age': '15', 'where': '東方地靈殿'}
"""
print(dict(config))
"""
{'DEFAULT': <Section: DEFAULT>, 
 'satori': <Section: satori>, 
 'koishi': <Section: koishi>, 
 'marisa': <Section: marisa>}
"""
print({k: dict(v) for k, v in config.items()})
"""
{'DEFAULT': {}, 
 'satori': {'name': '古明地覺', 
            'age': '16', 
            'where': '東方地靈殿'}, 
 'koishi': {'name': '古明地戀', 
            'age': '15', 
            'where': '東方地靈殿'}, 
 'marisa': {'name': '霧雨魔理沙', 
            'age': '17', 
            'where': '魔法森林'}}
"""

可以看到還是比較容易的,因爲 ini 這種文件格式本身就很簡單。除了讀取文件,我們還可以進行寫入。

import configparser

# 實例化一個 ConfigParser 類的實例
config = configparser.ConfigParser()
config["basic"] = {"Host""127.0.0.1",
                   "Port""8888",
                   "Username""satori"}

config["thread"] = {}
config["thread"]["name"] = "my_thread"
config["thread"]["num"] = "3"

with open("cfg.ini""w"encoding="utf-8") as f:
    config.write(f)

雖然成功寫入了,但是我們看到結果變成了小寫。是的,對於 parameter 來說,無論是大寫還是小寫,寫入文件的時候都會變成小寫。然後讀取也是,無論 ini 文件中是大寫還是小寫,讀取之後都會變成小寫。

注意:大小寫不敏感只是針對於 parameter,對於 section 來說還是區分大小寫的。

特殊格式

我們上面配置的 parameter 中的 key, value 都是一個普通的單詞,但其實我們還可以配置的更加複雜一些。

我們操作一波,看看能否正常解析。

import configparser

# 實例化一個 ConfigParser 實例
config = configparser.ConfigParser()
# 打開 ini 文件
config.read("cfg.ini"encoding="utf-8")

print(dict(config["簡單值"]))
"""
{'鍵': '值', 
 '鍵 裏面 有空格': '合法', 
 '值 裏面 有空格': '也 合 法', 
 '等號 周圍 有 空格': '仍然合法', 
 '你也使用': '代替等號'}
"""

print(dict(config["所有值都是字符串"]))
"""
{'這是字符串': '123', 
 '這也是字符串': '3.14', 
 '整數、浮點數、布爾值都是字符串': 'true'}
"""
# true True yes 都可以轉成布爾值 True
# false False no 都可以轉成布爾值 False
print(config["所有值都是字符串"].getboolean(
    '整數、浮點數、布爾值都是字符串'))
"""
True
"""

print(dict(config["值佔多行"]))
"""
{'洪世賢': '你怎麼穿品如的衣服啊\n還用人東西'}
"""

print(dict(config["值爲空字符串"]))
"""
{'key1': '', 'key2': ''}
"""

結果是正常的,但是很明顯上面這種做法有點閒的沒事了,以後就統一寫成 key = value 的形式即可。另外如果 ini 文件中只有 key 沒有 value 的話,默認是報錯的,但可以通過一個參數改變這一點:

import configparser

# "key =" 這種形式不叫沒有值,它是有值的,值爲空字符串
# "key" 這種形式纔是沒有值,解析的時候默認會報錯
# 可以通過一個參數改變這一點
config = configparser.ConfigParser(allow_no_value=True)
config.read_string(
    """
[mysqld]
user = mysql
skip-bdb
""")
print(dict(config["mysqld"]))
"""
{'user': 'mysql', 'skip-bdb': None}
"""

除此之外,name 之間還可以發生引用。

import configparser

config = configparser.ConfigParser()

# 可以通過 %(key)s 的方式對同一個 section 中的其它 key 進行引用
# 所以如果想表示一個 % 的話,需要寫兩個 %,因爲涉及到轉義
config.read_string("""
[section1]
user = 古明地覺
age = 16
info = %(user)s--%(age)s 
percent = 80%% 
""")

print(dict(config["section1"]))
"""
{'user': '古明地覺', 'age': '16', 
 'info': '古明地覺--16', 'percent': '80%'}
"""

還是很簡單的,如果想引用其它的 section 中的 name 要怎麼做呢?

import configparser

# 指定該參數之後,我們就不能通過 %(name)s 的方式引用了
# 需要使用 ${name} 這種格式,顯然更方便了
config = configparser.ConfigParser(
    interpolation=configparser.ExtendedInterpolation()
)

config.read_string("""
[DEFAULT]
默認的 = 自動加入到每一個 section 中

[section1]
user = 古明地覺
age = 16
info = ${user} -- ${age} 

[section2]
info = ${section1:user}${section1:age}
""")

print(dict(config["section1"]))
"""
{'user': '古明地覺', 
 'age': '16', 
 'info': '古明地覺 -- 16', 
 '默認的': '自動加入到每一個 section 中'}
"""
print(dict(config["section2"]))
"""
{'info': '古明地覺, 16', 
 '默認的': '自動加入到每一個 section 中'}
"""

小結

以上就是 ini 文件的一些簡單用法,以後我們在寫配置的時候,不妨使用一些專門用來表示配置的文件格式,不一定非要寫在 py 文件裏面。

而且使用 ini 等配置文件的一個好處就是,即便不懂 Python 的人也能看懂;或者這個配置文件不一定是要由你來寫,可能是別人寫,而那個人不用 Python,但是通過 ini 文件的話就省去了溝通的成本。

後續我們繼續介紹其它種類的配置文件,比如 yaml, toml,因爲 ini 雖然簡單,但表達能力還是很有限的。而 yaml 和 toml 的表達能力要更豐富,應用領域也要更廣一些。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/SZexbcAhVTe1X_dnAD8_hw