您好,登錄后才能下訂單哦!
Environs是解析環(huán)境變量的Python庫。它的開發(fā)受envparse啟發(fā),底層使用marshmallow驗證并序列化值。
在運行一個項目的時候,我們經(jīng)常會遇到設(shè)置不同環(huán)境的需求,如設(shè)置是開發(fā)環(huán)境、測試環(huán)境還是生產(chǎn)環(huán)境,或者在某些設(shè)置里面可能還需要設(shè)置一些變量開關(guān),如設(shè)置調(diào)試開關(guān)、日志開關(guān)、功能開關(guān)等等。
這些變量其實就是在項目運行時我們給項目設(shè)置的一些參數(shù)。這些參數(shù)一般情況來說,可以有兩種設(shè)置方法,一種是通過命令行參數(shù),一種是通過環(huán)境變量。二者的適用范圍不同,在不同的場景下我們可以選用更方便的方式來實現(xiàn)參數(shù)的設(shè)置。
本節(jié)我們以 Python 項目為例,說說環(huán)境變量的設(shè)置。
設(shè)置和獲取環(huán)境變量
首先,我們先來了解一下在 Python 項目里面怎樣設(shè)置和獲取變量。
首先讓我們定義一個最簡單的 Python 文件,命名為 main.py,內(nèi)容如下:
import os print(os.environ['VAR1'])
在這里我們導(dǎo)入了 os 模塊,它的 environ 對象里面就包含了當(dāng)前運行狀態(tài)下的所有環(huán)境變量,它其實是一個 os._Environ 對象,我們可以通過類似字典取值的方式從中獲取里面包含的環(huán)境變量的值,如代碼所示。
好,接下來我們什么也不設(shè)置,直接運行,看下結(jié)果:
python3 main.py
結(jié)果如下:
raise KeyError(key) from None
KeyError: 'VAR1'
直接拋出來了一個錯誤,這很正常,我們此時并沒有設(shè)置 VAR1 這個環(huán)境變量,當(dāng)然會拋出鍵值異常的錯誤了。
接下來我們在命令行下進(jìn)行設(shè)置,運行如下命令:
VAR1=germey python3 main.py
運行結(jié)果如下:
germey
可以看到我們在運行之前,在命令行之前通過鍵值對的形式對環(huán)境變量進(jìn)行設(shè)置,程序就可以獲取到 VAR1 這個值了,成功打印出來了 germey。
但這個環(huán)境變量是永久的嗎?我們這次再運行一遍原來的命令:
python3 main.py
結(jié)果如下:
raise KeyError(key) from None
KeyError: 'VAR1'
嗯,又拋錯了。
這說明了什么,在命令行的前面加上的這個環(huán)境變量聲明只能對當(dāng)前執(zhí)行的命令生效。
好,那既然如此,我難道每次運行都要在命令行前面加上這些聲明嗎?那豈不麻煩死了。
當(dāng)然有解決方法,我們使用 export 就可以了。
比如這里,我們執(zhí)行如下命令:
export VAR1=germey
執(zhí)行完這個命令之后,當(dāng)前運行環(huán)境下 VAR1 就被設(shè)置成功了,下面我們運行的命令都能獲取到 VAR1 這個環(huán)境變量了。
下面來試試,還是執(zhí)行原來的命令:
python3 main.py
結(jié)果如下:
germey
可以,成功獲取到了 VAR1 這個變量,后面我們運行的每一個命令就都會生效了。
但等一下,這個用了 export 就是永久生效了嗎?
其實并不是,其實這個 export 只對當(dāng)前的命令行運行環(huán)境生效,我們只要把命令行關(guān)掉再重新打開,之前用 export 設(shè)置的環(huán)境變量就都沒有了。
可以試試,重新打開命令行,再次執(zhí)行原來的命令,就會又拋出鍵值異常的錯誤了。
那又有同學(xué)會問了,我要在每次命令行運行時都想自動設(shè)置好環(huán)境變量怎么辦呢?
這個就更好辦了,只需要把 export 的這些命令加入到 ~/.bashrc 文件里面就好了,每次打開命令行的時候,系統(tǒng)都會自動先執(zhí)行以下這個腳本里面的命令,這樣環(huán)境變量就設(shè)置成功了。當(dāng)然這里面還有很多不同的文件,如 ~/.bash_profile 、~/.zshrc 、~/.profile、/etc/profile 等等,其加載是有先后順序的,大家感興趣可以去了解下。
好了,扯遠(yuǎn)了,我們現(xiàn)在已經(jīng)了解了如何設(shè)置環(huán)境變量和基本的環(huán)境變量獲取方法了。
更安全的獲取方式
但是上面的這種獲取變量的方式實際上是非常不友好的,萬一這個環(huán)境變量沒設(shè)置好,那豈不是就報錯了,這是很不安全的。
所以,下面再介紹幾種比較友好的獲取環(huán)境變量的方式,即使沒有設(shè)置過,也不會報錯。
我們可以把中括號取值的方式改成 get 方法,如下所示:
import os print(os.environ.get('VAR1'))
這樣就不會報錯了,如果 VAR1 沒設(shè)置,會直接返回 None,而不是直接報錯。
另外我們也可以給 get 方法傳入第二個參數(shù),表示默認(rèn)值,如下所示:
import os print(os.environ.get('VAR1', 'germey'))
這樣即使我們?nèi)绻O(shè)置過 VAR1,他就會用 germey 這個字符串代替,這就完成了默認(rèn)環(huán)境變量的設(shè)置。
下面還有幾種獲取環(huán)境變量的方式,總結(jié)如下:
import os print(os.getenv('VAR1', 'germey'))
這個方式比上面的寫法更簡單,功能完全一致。
弊端
但其實上面的方法有一個不方便的地方,如果我們想要設(shè)置非字符串類型的環(huán)境變量怎么辦呢?比如設(shè)置 int 類型、float 類型、list 類型,可能我們的寫法就會變成這個樣子:
import os import json VAR1 = int(os.getenv('VAR1', 1)) VAR2 = float(os.getenv('VAR2', 5.5)) VAR3 = json.loads(os.getenv('VAR3'))
然后設(shè)置環(huán)境變量的時候就變成這樣子:
export VAR1=1 export VAR2=2.3 export VAR3='["1", "2"]'
這樣才能成功獲取到結(jié)果,打印出來結(jié)果如下:
1
2.3
['1', '2']
不過看下這個,寫法也太奇葩了吧,又是類型轉(zhuǎn)換,又是 json 解析什么的,有沒有更好的方法來設(shè)置。
environs
當(dāng)然有的,下面推薦一個 environs 庫,利用它我們可以輕松地設(shè)置各種類型的環(huán)境變量。
這是一個第三方庫,可以通過 pip 來安裝:
pip3 install environs
好,安裝之后,我們再來體驗一下使用 environs 來設(shè)置環(huán)境變量的方式。
from environs import Env env = Env() VAR1 = env.int('VAR1', 1) VAR2 = env.float('VAR2', 5.5) VAR3 = env.list('VAR3')
這里 environs 直接提供了 int、float、list 等方法,我們就不用再去進(jìn)行類型轉(zhuǎn)換了。
與此同時,設(shè)置環(huán)境變量的方式也有所變化:
export VAR1=1 export VAR2=2.3 export VAR3=1,2
這里 VAR3 是列表,我們可以直接用逗號分隔開來。
打印結(jié)果如下:
1
2.3
['1', '2']
官方示例
下面我們再看一個官方示例,這里示例了一些常見的用法。
首先我們來定義一些環(huán)境變量,如下:
export GITHUB_USER=sloria export MAX_CONNECTIONS=100 export SHIP_DATE='1984-06-25' export TTL=42 export ENABLE_LOGIN=true export GITHUB_REPOS=webargs,konch,ped export COORDINATES=23.3,50.0 export LOG_LEVEL=DEBUG
這里有字符串、有日期、有日志級別、有字符串列表、有浮點數(shù)列表、有布爾。
我們來看下怎么獲取,寫法如下:
from environs import Env env = Env() env.read_env() # read .env file, if it exists # required variables gh_user = env("GITHUB_USER") # => 'sloria' secret = env("SECRET") # => raises error if not set # casting max_connections = env.int("MAX_CONNECTIONS") # => 100 ship_date = env.date("SHIP_DATE") # => datetime.date(1984, 6, 25) ttl = env.timedelta("TTL") # => datetime.timedelta(0, 42) log_level = env.log_level("LOG_LEVEL") # => logging.DEBUG # providing a default value enable_login = env.bool("ENABLE_LOGIN", False) # => True enable_feature_x = env.bool("ENABLE_FEATURE_X", False) # => False # parsing lists gh_repos = env.list("GITHUB_REPOS") # => ['webargs', 'konch', 'ped'] coords = env.list("COORDINATES", subcast=float) # => [23.3, 50.0]
通過觀察代碼可以發(fā)現(xiàn)它提供了這些功能:
可以說有了這些方法,定義各種類型的變量都不再是問題了。
支持類型
總的來說,environs 支持的轉(zhuǎn)化類型有這么多:
env.str
env.bool
env.int
env.float
env.decimal
env.list (accepts optional subcast keyword argument)
env.dict (accepts optional subcast keyword argument)
env.json
env.datetime
env.date
env.timedelta (assumes value is an integer in seconds)
env.url
env.uuid
env.log_level
env.path (casts to a pathlib.Path)
這里 list、dict、json、date、url、uuid、path 個人認(rèn)為都還是比較有用的,另外 list、dict 方法還有一個 subcast 方法可以對元素內(nèi)容進(jìn)行轉(zhuǎn)化。
對于 dict、url、date、uuid、path 這里我們來補充說明一下。
下面我們定義這些類型的環(huán)境變量:
export VAR_DICT=name=germey,age=25 export VAR_JSON='{"name": "germey", "age": 25}' export VAR_URL=https://cuiqingcai.com export VAR_UUID=762c8d53-5860-4d5d-81bc-210bf2663d0e export VAR_PATH=/var/py/env
需要注意的是,DICT 的解析,需要傳入的是逗號分隔的鍵值對,JSON 的解析是需要傳入序列化的字符串。
解析寫法如下:
from environs import Env env = Env() VAR_DICT = env.dict('VAR_DICT') print(type(VAR_DICT), VAR_DICT) VAR_JSON = env.json('VAR_JSON') print(type(VAR_JSON), VAR_JSON) VAR_URL = env.url('VAR_URL') print(type(VAR_URL), VAR_URL) VAR_UUID = env.uuid('VAR_UUID') print(type(VAR_UUID), VAR_UUID) VAR_PATH = env.path('VAR_PATH') print(type(VAR_PATH), VAR_PATH)
運行結(jié)果如下:
<class 'dict'> {'name': 'germey', 'age': '25'}
<class 'dict'> {'name': 'germey', 'age': 25}
<class 'urllib.parse.ParseResult'> ParseResult(scheme='https', netloc='cuiqingcai.com', path='', params='', query='', fragment='')
<class 'uuid.UUID'> 762c8d53-5860-4d5d-81bc-210bf2663d0e
<class 'pathlib.PosixPath'> /var/py/env
可以看到,它分別給我們轉(zhuǎn)化成了 dict、dict、ParseResult、UUID、PosixPath 類型了。
在代碼中直接使用即可。
文件讀取
如果我們的一些環(huán)境變量是定義在文件中的,environs 還可以進(jìn)行讀取和加載,默認(rèn)會讀取本地當(dāng)前運行目錄下的 .env 文件。
示例如下:
from environs import Env env = Env() env.read_env() APP_DEBUG = env.bool('APP_DEBUG') APP_ENV = env.str('APP_ENV') print(APP_DEBUG) print(APP_ENV)
下面我們在 .env 文件中寫入如下內(nèi)容:
APP_DEBUG=false
APP_ENV=prod
運行結(jié)果如下:
False
prod
沒問題,成功讀取。
當(dāng)然我們也可以自定義讀取的文件,如 .env.test 文件,內(nèi)容如下:
APP_DEBUG=false
APP_ENV=test
代碼則可以這么定義:
from environs import Env env = Env() env.read_env(path='.env.test') APP_DEBUG = env.bool('APP_DEBUG') APP_ENV = env.str('APP_ENV')
這里就通過 path 傳入了定義環(huán)境變量的文件路徑即可。
前綴處理
environs 還支持前綴處理,一般來說我們定義一些環(huán)境變量,如數(shù)據(jù)庫的連接,可能有 host、port、password 等,但在定義環(huán)境變量的時候往往會加上對應(yīng)的前綴,如 MYSQL_HOST、MYSQL_PORT、MYSQL_PASSWORD 等,但在解析時,我們可以根據(jù)前綴進(jìn)行分組處理,見下面的示例:
# export MYAPP_HOST=lolcathost # export MYAPP_PORT=3000 with env.prefixed("MYAPP_"): host = env("HOST", "localhost") # => 'lolcathost' port = env.int("PORT", 5000) # => 3000 # nested prefixes are also supported: # export MYAPP_DB_HOST=lolcathost # export MYAPP_DB_PORT=10101 with env.prefixed("MYAPP_"): with env.prefixed("DB_"): db_host = env("HOST", "lolcathost") db_port = env.int("PORT", 10101)
可以看到這里通過 with 和 priefixed 方法組合使用即可實現(xiàn)分區(qū)處理,這樣在每個分組下再賦值到一個字典里面即可。
合法性驗證
有些環(huán)境變量的傳入是不可預(yù)知的,如果傳入一些非法的環(huán)境變量很可能導(dǎo)致一些難以預(yù)料的問題。比如說一些可執(zhí)行的命令,通過環(huán)境變量傳進(jìn)來,如果是危險命令,那么會非常危險。
所以在某些情況下我們需要驗證傳入的環(huán)境變量的有效性,看下面的例子:
# export TTL=-2 # export NODE_ENV='invalid' # export EMAIL='^_^' from environs import Env from marshmallow.validate import OneOf, Length, Email env = Env() # simple validator env.int("TTL", validate=lambda n: n > 0) # => Environment variable "TTL" invalid: ['Invalid value.'] # using marshmallow validators env.str( "NODE_ENV", validate=OneOf( ["production", "development"], error="NODE_ENV must be one of: {choices}" ), ) # => Environment variable "NODE_ENV" invalid: ['NODE_ENV must be one of: production, development'] # multiple validators env.str("EMAIL", validate=[Length(min=4), Email()]) # => Environment variable "EMAIL" invalid: ['Shorter than minimum length 4.', 'Not a valid email address.']
在這里,我們通過 validate 方法,并傳入一些判斷條件。如 NODE_ENV 只允許傳入 production 和 develpment 其中之一;EMAIL 必須符合 email 的格式。
這里依賴于 marshmallow 這個庫,里面有很多驗證條件,大家可以了解下。
如果不符合條件的,會直接拋錯,例如:
marshmallow.exceptions.ValidationError: ['Invalid value.']
關(guān)于 marshmallow 庫的用法,大家可以參考:https://marshmallow.readthedocs.io/en/stable/,后面我也抽空寫一下介紹下。
最后再附一點我平時定義環(huán)境變量的一些常見寫法,如:
import platform from os.path import dirname, abspath, join from environs import Env from loguru import logger env = Env() env.read_env() # definition of flags IS_WINDOWS = platform.system().lower() == 'windows' # definition of dirs ROOT_DIR = dirname(dirname(abspath(__file__))) LOG_DIR = join(ROOT_DIR, env.str('LOG_DIR', 'logs')) # definition of environments DEV_MODE, TEST_MODE, PROD_MODE = 'dev', 'test', 'prod' APP_ENV = env.str('APP_ENV', DEV_MODE).lower() APP_DEBUG = env.bool('APP_DEBUG', True if APP_ENV == DEV_MODE else False) APP_DEV = IS_DEV = APP_ENV == DEV_MODE APP_PROD = IS_PROD = APP_DEV == PROD_MODE APP_TEST = IS_TEST = APP_ENV = TEST_MODE # redis host REDIS_HOST = env.str('REDIS_HOST', '127.0.0.1') # redis port REDIS_PORT = env.int('REDIS_PORT', 6379) # redis password, if no password, set it to None REDIS_PASSWORD = env.str('REDIS_PASSWORD', None) # redis connection string, like redis://[password]@host:port or rediss://[password]@host:port REDIS_CONNECTION_STRING = env.str('REDIS_CONNECTION_STRING', None) # definition of api API_HOST = env.str('API_HOST', '0.0.0.0') API_PORT = env.int('API_PORT', 5555) API_THREADED = env.bool('API_THREADED', True) # definition of flags ENABLE_TESTER = env.bool('ENABLE_TESTER', True) ENABLE_GETTER = env.bool('ENABLE_GETTER', True) ENABLE_SERVER = env.bool('ENABLE_SERVER', True) # logger logger.add(env.str('LOG_RUNTIME_FILE', 'runtime.log'), level='DEBUG', rotation='1 week', retention='20 days') logger.add(env.str('LOG_ERROR_FILE', 'error.log'), level='ERROR', rotation='1 week')
這里定義了一些開發(fā)環(huán)境、日志路徑、數(shù)據(jù)庫連接、API 設(shè)置、開關(guān)設(shè)置等等,是從我之前寫的一個代理池項目拿來的,大家可以參考:https://github.com/Python3WebSpider/ProxyPool。
總結(jié)
到此這篇關(guān)于Python 使用 environs 庫來更好地定義環(huán)境變量的文章就介紹到這了,更多相關(guān)python 使用 environs 庫定義環(huán)境變量內(nèi)容請搜索億速云以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持億速云!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。