背景
最近在結(jié)合 Python-3.12.0a6 版本開發(fā)一個多線程架構(gòu)的后臺服務(wù);服務(wù)啟動時會讀取配置文件,并且要求所有線程共享同一份配置。
服務(wù)本身直接通過 http 接口來動態(tài)調(diào)整配置項的值,還要做到服務(wù)退出之后持久化配置項到配置文件。
一開始以為這個用 Python 寫也會要用幾百行,最后發(fā)現(xiàn)完成核心功能就只需要不到 50 行,Python 牛逼?。?!
需求一:支持簡單的配置項
假設(shè)我們目前只支持 name 和 port 兩個配置項,多支持幾個不難,只是不方便演示。
"""實例配置管理
"""
from dataclasses import dataclass
class Config(object):
name:str= "mysql"
port:int = 3306
看起來是沒問題了,下面可以試用一下,也順帶引導出第二個需求。
In [6]: a = Config()
In [7]: b = Config()
In [8]: id(a)
4407850896
: In [9]: id(b)
4407852496 :
可以看到兩個配置對象的 ID 值不一樣。由于配置文件只有一個,我們希望配置對象也只有一個。
需求二:配置對象全局唯一
交代一個背景,解釋器在做 import 的時候是單一線程在跑的。有了這個前提我們可以少寫一些加鎖的代碼,能少寫一行算一行吧。
"""實例配置管理
"""
from dataclasses import dataclass
class Config(object):
name:str= "mysql"
port:int = 3306
_instance = None
# 單例模式
def __new__(cls, *args, **kw):
if cls._instance is None:
cls._instance = object.__new__(cls, *args, **kw)
return cls._instance
用 Python 就是這么的簡單,幾行代碼就搞定了。但是還是要測試一下順帶引導出下一個需求。
In [4]: a = Config()
In [5]: b = Config()
In [6]: id(a)
4414751568
: In [7]: id(b)
4414751568 :
現(xiàn)在配置對象已經(jīng)是單例了,但還有一個問題,它的每個配置項的值都是默認值,我們當然是希望它在創(chuàng)建對象的時候是使用配置文件中的值啦。下面看需求三怎么實現(xiàn)。
需求三:根據(jù)配置文件構(gòu)造配置對象
假設(shè)我們的配置文件被 “持久化” 到了 /tmp/config.json ,現(xiàn)在就可以寫讀取配置文件并更新配置對象值的代碼了。
"""實例配置管理
"""
import json
import logging
from pathlib import Path
from dataclasses import dataclass
class Config(object):
name:str= "mysql"
port:int = 3306
_instance = None
# 單例模式
def __new__(cls, *args, **kw):
if cls._instance is None:
cls._instance = object.__new__(cls, *args, **kw)
return cls._instance
# 讀取配置文件
def __post_init__(self):
"""如果配置文件存在就用配置文件中的值,覆蓋默認值。在這個過程中如果遇到異常就保持默認值
"""
if (config_file:=Path("/tmp/config.json")) and config_file.exists():
try:
with open(config_file) as f:
json_data = json.loads(f.read())
self.__dict__.update(json_data)
except Exception as err:
pass
else:
logging.warn("config file '{}' not exists. well using defautl values .".format(config_file))
假設(shè)我們的配置文件內(nèi)容是這樣的。
cat /tmp/config.json
{
"name": "trump",
"port": 8848
}
下面的測試一下
In [2]: a = Config()
In [3]: a
Out[3]: Config(name='trump', port=8848)
In [4]: b = Config()
In [5]: b
Out[5]: Config(name='trump', port=8848)
In [6]: a == b
Out[6]: True
可以看到 name 和 port 已經(jīng)沒有使用默認的 "mysql" 和 3306 了,而是使用了配置文件中的值。
到這里我們只剩下最后一個需求,就是在程序退出的時候,把配置對象的值更新回配置文件。這個就看需求四怎么寫。
需求四:程序退出前自動持久化配置對象到配置文件
解釋器在退出前有個鉤子(atexit),我們可以在這里指定回調(diào)函數(shù),這個時候保存配置文件再適合不過。
"""實例配置管理
"""
import json
import atexit
import logging
from pathlib import Path
from dataclasses import dataclass, asdict
class Config(object):
name:str= "mysql"
port:int = 3306
_instance = None
# 單例模式
def __new__(cls, *args, **kw):
if cls._instance is None:
cls._instance = object.__new__(cls, *args, **kw)
return cls._instance
# 讀取配置文件
def __post_init__(self):
"""如果配置文件存在就用配置文件中的值,覆蓋默認值;在這個過程中如果遇到異常就保持默認值。程序退出時持久到到配置到文件。
"""
if (config_file:=Path("/tmp/config.json")) and config_file.exists():
try:
with open(config_file) as f:
json_data = json.loads(f.read())
self.__dict__.update(json_data)
except Exception as err:
pass
else:
logging.warn("config file '{}' not exists. well using defautl values .".format(config_file))
# 程序退出時保存配置到配置文件 /tmp/config.json
def sync_to_disk():
"""
"""
json_str = json.dumps(asdict(self), indent=4)
with open(config_file, 'w') as f:
logging.warning("save configs to '{}' ".format(config_file))
f.write(json_str)
atexit.register(sync_to_disk)
驗證一下
In [1]: from appconfig import Config
In [2]: a = Config()
In [3]: a.name
Out[3]: 'trump'
In [4]: a.name = "hello-world"
In [5]: exit()
WARNINGsave configs to '/tmp/config.json'
看日志是已經(jīng)把配置項更新回配置文件了,但是還是 cat 確認一下為好。
cat /tmp/config.json
{
"name": "hello-world",
"port": 8848
}
可以看到確實已經(jīng)把配置項的值更新到文件了。
審核編輯 :李倩
-
程序
+關(guān)注
關(guān)注
116文章
3756瀏覽量
80751 -
配置
+關(guān)注
關(guān)注
1文章
187瀏覽量
18340 -
python
+關(guān)注
關(guān)注
55文章
4767瀏覽量
84375
原文標題:Python 程序配置文件管理的最佳工程實踐
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論