若是咱們在較複雜的項目中不使用配置文件,咱們可能會面臨下面的狀況:python
可是,若是你使用了配置管理呢,那會有如下幾個優勢:mysql
因爲使用 Python 較多,所以基於 Python 進行配置管理的相關說明,固然其餘語言也都是大同小異,主要思想仍是不變。git
咱們很天然就能想到這一點,例如如下代碼:github
# main.py import pymysql DATABASE_CONFIG = { 'host':'localhost', 'dbname':'db', 'user':'user', 'password':'pwd', 'port':3306 } def connect_db_do_something1(dbname): if dbname != config.DATABASE_CONFIG['dbname']: raise ValueError("Couldn't not find DB with given name") conn = pymysql.connect(host=config.DATABASE_CONFIG['host'], user=config.DATABASE_CONFIG['user'], password=config.DATABASE_CONFIG['password'], db=config.DATABASE_CONFIG['dbname']) ''' do something 1 ''' def connect_db_do_something2(dbname): if dbname != config.DATABASE_CONFIG['dbname']: raise ValueError("Couldn't not find DB with given name") conn = pymysql.connect(host=config.DATABASE_CONFIG['host'], user=config.DATABASE_CONFIG['user'], password=config.DATABASE_CONFIG['password'], db=config.DATABASE_CONFIG['dbname']) ''' do something 2 ''' connect_db_do_something1('db') connect_db_do_something2('db')
在上面的代碼中,咱們能夠看到,同一數據庫配置,咱們反覆使用了兩次,若是咱們須要更改數據庫相關的數據如password,咱們不須要在兩個方法內部修改,而是隻用修改DATABASE_CONFIG字典中的相關值便可。和之前沒有配置管理的時候相比,減小了太多的工做量了。web
可是當你的項目開始變得複雜的時候,你的文件就不止一個這麼簡單了,這時候若是我須要在 main2.py 裏面須要用 DATABASE_CONFIG 的時候就不是很方便了,由於若是直接 import main 的時候,雖然可以使用 main.DATABASE_CONFIG ,但同時 mian.py 中的sql
connect_db_do_something1('db') connect_db_do_something2('db')
也被執行了,這可不是咱們想看到的,所以咱們有了新的需求,能在同一個項目下的不一樣文件裏簡單快速的導入咱們的數據庫配置 DATABASE_CONFIG,因而咱們想出了下面的方法來解決這個問題:數據庫
# config.py DATABASE_CONFIG = { 'host': 'localhost', 'dbname': 'db', 'user': 'user', 'password': 'pwd', 'port': 3306 } # main1.py import pymysql import config def connect_db_do_something1(dbname): if dbname != config.DATABASE_CONFIG['dbname']: raise ValueError("Couldn't not find DB with given name") conn = pymysql.connect(host=config.DATABASE_CONFIG['host'], user=config.DATABASE_CONFIG['user'], password=config.DATABASE_CONFIG['password'], db=config.DATABASE_CONFIG['dbname']) ''' do something 1 ''' connect_db_do_something1('db') # main2.py import pymysql import config def connect_db_do_something2(dbname): if dbname != config.DATABASE_CONFIG['dbname']: raise ValueError("Couldn't not find DB with given name") conn = pymysql.connect(host=config.DATABASE_CONFIG['host'], user=config.DATABASE_CONFIG['user'], password=config.DATABASE_CONFIG['password'], db=config.DATABASE_CONFIG['dbname']) ''' do something 2 ''' connect_db_do_something2('db')
按照上面的代碼,咱們能夠在兩個不一樣的文件 main1.py 和 main2.py 中分別引用 config.py 中配置了,咱們的配置管理看起來更進一步了。編程
有可能咱們的項目須要多個配置文件,好比測試環境和生產環境。先從單個文件講起,咱們能夠採用以下解決方案:json
# config.py class Config: APP_NAME = 'myapp' SECRET_KEY = 'secret-key-of-myapp' ADMIN_NAME = 'administrator' AWS_DEFAULT_REGION = 'ap-northeast-2' STATIC_PREFIX_PATH = 'static' ALLOWED_IMAGE_FORMATS = ['jpg', 'jpeg', 'png', 'gif'] MAX_IMAGE_SIZE = 5242880 # 5MB class DevelopmentConfig(Config): DEBUG = True AWS_ACCESS_KEY_ID = 'aws-access-key-for-dev' AWS_SECERT_ACCESS_KEY = 'aws-secret-access-key-for-dev' AWS_S3_BUCKET_NAME = 'aws-s3-bucket-name-for-dev' DATABASE_URI = 'database-uri-for-dev' class TestConfig(Config): DEBUG = True TESTING = True AWS_ACCESS_KEY_ID = 'aws-access-key-for-test' AWS_SECERT_ACCESS_KEY = 'aws-secret-access-key-for-test' AWS_S3_BUCKET_NAME = 'aws-s3-bucket-name-for-test' DATABASE_URI = 'database-uri-for-dev' class ProductionConfig(Config): DEBUG = False AWS_ACCESS_KEY_ID = 'aws-access-key-for-prod' AWS_SECERT_ACCESS_KEY = 'aws-secret-access-key-for-prod' AWS_S3_BUCKET_NAME = 'aws-s3-bucket-name-for-prod' DATABASE_URI = 'database-uri-for-dev' # main.py import sys import config import sys import config ''' do some important things ''' if __name__ == '__main__': env = sys.argv[1] if len(sys.argv) > 2 else 'dev' if env == 'dev': app.config = config.DevelopmentConfig elif env == 'test': app.config = config.TestConfig elif env == 'prod': app.config = config.ProductionConfig else: raise ValueError('Invalid environment name')
這樣咱們就能夠從一個配置文件中獲取不一樣級別的不一樣配置了。安全
和上面相似,只不過換成了從不一樣的文件中讀取同一個配置文件的不一樣配置:
# config.py class Config: APP_NAME = 'myapp' SECRET_KEY = 'secret-key-of-myapp' ADMIN_NAME = 'administrator' AWS_DEFAULT_REGION = 'ap-northeast-2' STATIC_PREFIX_PATH = 'static' ALLOWED_IMAGE_FORMATS = ['jpg', 'jpeg', 'png', 'gif'] MAX_IMAGE_SIZE = 5242880 # 5MB class DevelopmentConfig(Config): DEBUG = True AWS_ACCESS_KEY_ID = 'aws-access-key-for-dev' AWS_SECERT_ACCESS_KEY = 'aws-secret-access-key-for-dev' AWS_S3_BUCKET_NAME = 'aws-s3-bucket-name-for-dev' DATABASE_URI = 'database-uri-for-dev' class TestConfig(Config): DEBUG = True TESTING = True AWS_ACCESS_KEY_ID = 'aws-access-key-for-test' AWS_SECERT_ACCESS_KEY = 'aws-secret-access-key-for-test' AWS_S3_BUCKET_NAME = 'aws-s3-bucket-name-for-test' DATABASE_URI = 'database-uri-for-dev' class ProductionConfig(Config): DEBUG = False AWS_ACCESS_KEY_ID = 'aws-access-key-for-prod' AWS_SECERT_ACCESS_KEY = 'aws-secret-access-key-for-prod' AWS_S3_BUCKET_NAME = 'aws-s3-bucket-name-for-prod' DATABASE_URI = 'database-uri-for-dev' class CIConfig: SERVICE = 'travis-ci' HOOK_URL = 'web-hooking-url-from-ci-service' # main1.py import config dev_config = config.DevelopmentConfig ''' do something ''' # main2.py import config app.ci = config.CIConfig ''' do otherthing '''
這樣使用更加靈活了,從不一樣的文件裏讀取不一樣的配置,而咱們對於配置的增刪改只須要在 config.py 中進行,配置管理技能再次進階!
比起使用 Python 內建的數據結構,更加通用的方法是使用外部配置文件,由於這些文件只會被視爲配置文件,而不會像 config.py
同樣有代碼的屬性。外部配置文件的格式多種多樣,咱們在使用它的時候會根據文件格式有不一樣的讀取方式。例如:*.yaml
或者 *.yml
、*.json
、*.cfg
或 *.conf
、*.ini
, 甚至是你自定義的文件 *.yourname
。
YAML(/ˈjæməl/,尾音相似camel駱駝)是一個可讀性高,用來表達數據序列化的格式。YAML參考了其餘多種語言,包括:C語言、Python、Perl,並從XML、電子郵件的數據格式(RFC 2822)中得到靈感。Clark Evans在2001年首次發表了這種語言[1],另外Ingy döt Net與Oren Ben-Kiki也是這語言的共同設計者[2]。當前已經有數種編程語言或腳本語言支持(或者說解析)這種語言。 ----- 中文維基百科
YAML 看起來像下面這種格式:
mysql: host: localhost dbname: db user: user passwd: pwb port: 3306 other: host: other_host dbname: other_db user: other_user passwd: other_pwb port: 3306
能夠經過相似下面的代碼來讀取裏面的內容:
import yaml with open("config.yml", 'r') as ymlfile: cfg = yaml.load(ymlfile) print(cfg['mysql'])
將輸出如下內容
{'host': 'localhost', 'dbname': 'db', 'user': 'user', 'password': 'pwd', 'port': 3306}
若是須要從 python 寫入配置到 YAML 也很容易,只須要使用 yaml.dump(dict) 便可,dict 指的是配置的字典。更加詳細的內容能夠查看 PyYAML Documentation
INI文件是一個無固定標準格式的配置文件。它以簡單的文字與簡單的結構組成,經常使用在Windows操做系統,或是其餘操做系統上,許多程序也會採用INI文件作爲設置程序之用。Windows操做系統後來以註冊表的形式取代掉INI檔。INI文件的命名來源,是取自英文「初始(Initial)」的首字縮寫,正與它的用途——初始化程序相應。有時候,INI文件也會以不一樣的擴展名,如「.CFG」、「.CONF」、或是「.TXT」代替。 ----- 中文維基百科
它長的像這樣:
[mysql] host=localhost dbname=db user=user passwd=pwd port=3306 [other] host=other_host dbname=other_db user=other_user passwd=other_pwb port=3306
經過如下代碼能夠讀取它:
import configparser config = configparser.ConfigParser() config.read("config.ini") host = config['mysql']['host'] print(host)
這將輸出 INI
配置文件中的 mysql section 中的 host 值
要寫入 INI 配置文件也很簡單,參考以下代碼便可:
import configparser config = configparser.ConfigParser() config.read("config.ini") config['mysql']['test_str'] = 'a test string' config.write(open("ini", "w"))
如今的配置文件會變成:
[mysql] host = localhost dbname = db user = user passwd = pwd port = 3306 test_str = a test string [other] host=other_host dbname=other_db user=other_user passwd=other_pwb port=3306
JSON是JavaScript對象表示法的縮寫。它很是普遍,所以對許多編程語言都有很好的支持。它的格式你們也很眼熟,看起來和 Python 中的字典很像:
{ "mysql":{ "host": "localhost", "dbname": "db", "user" : "user", "password": "pwd", "port": 3306 }, "other":{ "host": "other_host", "dbname": "other_db", "user": "other_user", "passwd": "other_pwb", "port": 3306 }
你能夠參考如下代碼讀取:
import json with open('config.json') as json_data_file: config = json.load(json_data_file) host = config['mysql']['host'] print(host) # output: localhost
要將配置寫入json中也很簡單,參考如下代碼:
import json with open('config.json') as json_data_file: config = json.load(json_data_file) config['mysql']['test_str'] = 'a test string' with open('config.json', 'w') as outfile: json.dump(config, outfile)
這樣就會獲得增長了配置的json文件了:
{ "mysql":{ "host": "localhost", "dbname": "db", "user" : "user", "password": "pwd", "port": 3306, "test_str" : "a test string" }, "other":{ "host": "other_host", "dbname": "other_db", "user": "other_user", "passwd": "other_pwb", "port": 3306 } }
其餘格式的文件大多如此,就不贅述了。而且外部的配置文件中也能夠配置多個配置(mysql, other等)
可是,回到咱們開篇講的問題,以上的兩種配置管理方案(使用 Python 內置的數據結構、使用外部配置文件) 都忽略了兩個問題:
其一,咱們如何應對安全數據直接曝光於公衆的可能問題呢,若是咱們須要使用版本控制系統例如 Github,或許咱們能夠嘗試將 config.py 文件放到 .gitignore 裏面,但咱們若是哪一天修改了倉庫,忘了將 config.py 忽略掉而 push 到了GitHub 上,那麼咱們的安全敏感信息仍然會向公衆泄露,因爲版本控制的存在,即便你刪掉了還會有這條提交記錄,處理起來會很麻煩。
其二,若是咱們要在咱們本地新開一個項目,這個項目也須要引用同樣的數據庫配置文件,或許咱們能夠找到第一個項目的文件夾,複製出 config.py 到 新的項目文件夾。嗯,看起來可行,可是,若是你要新開十幾個項目呢,幾百個項目呢?
所以咱們能夠引入下一種配置管理的方式,對解決上面提出的兩個問題都是較爲友好的解決方案,即便用環境變量,各類開發環境(Win、Mac、Linux)的系統環境變量的設置方式有所不一樣,能夠參考這篇文章。
另外 PyCharm 和 VS Code 有更加方便的配置方式,能夠爲不一樣的項目分配不一樣的設置。
PyCharm 中,在菜單 Run->Edit configurations 中,手動設置Environment variables
VS Code 中,在 Setting 中搜索 env ,在 Terminal 中選擇你的操做系統相關的Terminal > Integrated > Env: Your OS ,點進 settings.json 進行添加
使用環境變量配置值不用做爲單獨的文件進行管理,所以有較小的安全風險,它很容易使用,能夠在你的開發環境中的任何項目任何代碼庫中使用,可是它的管理方式可能有些複雜。有些環境沒法使用環境變量,好比Apache,Nginx等Web服務器,這時候就須要採用其餘的方式。
這種方法比利用 Python 內置的數據結構更加先進,內置數據結構的方法要求配置文件必需要在能夠直接 import 的路徑上。可是動態加載中,配置文件沒必要在可直接導入的路徑上,甚至能夠位於其餘存儲庫中,這樣的話,配置文件就和項目分隔開了,其餘的項目也能夠動態加載這個配置文件,例如:
# /opt/settings/config.py DATABASE_CONFIG = { 'host': 'localhost', 'dbname': 'company', 'user': 'user', 'password': 'password', 'port': 3306 } # main.py import sys import pymysql sys.path.append('/opt/settings') import config def connect_db(dbname): if dbname != config.DATABASE_CONFIG['dbname']: raise ValueError("Couldn't not find DB with given name") conn = pymysql.connect(host=config.DATABASE_CONFIG['host'], user=config.DATABASE_CONFIG['user'], password=config.DATABASE_CONFIG['password'], db=config.DATABASE_CONFIG['dbname']) return conn connect_db('company')
以上概括了四種配置管理的方式,整體來講沒有優劣之分,看我的的須要,甚至上面的幾種方法能夠混合使用,對於一些軟件項目,它自身可能就提供了相關的變量配置入口,好比 airbnb 的 Airflow 。並且,當系統規模很是大時,最好使用主要提供配置管理的第三方工具或服務。
注:本文爲轉載,原文地址:Python配置管理的幾種方式