Python 使用 environs 庫定義環(huán)境變量的方法
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 對象里面就包含了當前運行狀態(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)境變量,當然會拋出鍵值異常的錯誤了。
接下來我們在命令行下進行設(shè)置,運行如下命令:
VAR1=germey python3 main.py
運行結(jié)果如下:
germey
可以看到我們在運行之前,在命令行之前通過鍵值對的形式對環(huán)境變量進行設(shè)置,程序就可以獲取到 VAR1 這個值了,成功打印出來了 germey。
但這個環(huán)境變量是永久的嗎?我們這次再運行一遍原來的命令:
python3 main.py
結(jié)果如下:
raise KeyError(key) from None
KeyError: 'VAR1'
嗯,又拋錯了。
這說明了什么,在命令行的前面加上的這個環(huán)境變量聲明只能對當前執(zhí)行的命令生效。
好,那既然如此,我難道每次運行都要在命令行前面加上這些聲明嗎?那豈不麻煩死了。
當然有解決方法,我們使用 export 就可以了。
比如這里,我們執(zhí)行如下命令:
export VAR1=germey
執(zhí)行完這個命令之后,當前運行環(huán)境下 VAR1 就被設(shè)置成功了,下面我們運行的命令都能獲取到 VAR1 這個環(huán)境變量了。
下面來試試,還是執(zhí)行原來的命令:
python3 main.py
結(jié)果如下:
germey
可以,成功獲取到了 VAR1 這個變量,后面我們運行的每一個命令就都會生效了。
但等一下,這個用了 export 就是永久生效了嗎?
其實并不是,其實這個 export 只對當前的命令行運行環(huán)境生效,我們只要把命令行關(guān)掉再重新打開,之前用 export 設(shè)置的環(huán)境變量就都沒有了。
可以試試,重新打開命令行,再次執(zhí)行原來的命令,就會又拋出鍵值異常的錯誤了。
那又有同學會問了,我要在每次命令行運行時都想自動設(shè)置好環(huán)境變量怎么辦呢?
這個就更好辦了,只需要把 export 的這些命令加入到 ~/.bashrc 文件里面就好了,每次打開命令行的時候,系統(tǒng)都會自動先執(zhí)行以下這個腳本里面的命令,這樣環(huán)境變量就設(shè)置成功了。當然這里面還有很多不同的文件,如 ~/.bash_profile 、~/.zshrc 、~/.profile、/etc/profile 等等,其加載是有先后順序的,大家感興趣可以去了解下。
好了,扯遠了,我們現(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ù),表示默認值,如下所示:
import os
print(os.environ.get('VAR1', 'germey'))
這樣即使我們?nèi)绻O(shè)置過 VAR1,他就會用 germey 這個字符串代替,這就完成了默認環(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
當然有的,下面推薦一個 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 等方法,我們就不用再去進行類型轉(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)它提供了這些功能:
- 通過 env 可以設(shè)置必需定義的變量,如果沒有定義,則會報錯。
- 通過 date、timedelta 方法可以對日期或時間進行轉(zhuǎn)化,轉(zhuǎn)成 datetime.date 或 timedelta 類型。
- 通過 log_level 方法可以對日志級別進行轉(zhuǎn)化,轉(zhuǎn)成 logging 里的日志級別定義。
- 通過 bool 方法可以對布爾類型變量進行轉(zhuǎn)化。
- 通過 list 方法可以對逗號分隔的內(nèi)容進行 list 轉(zhuǎn)化,并可以通過 subcast 方法對 list 的每個元素進行類型轉(zhuǎ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 個人認為都還是比較有用的,另外 list、dict 方法還有一個 subcast 方法可以對元素內(nèi)容進行轉(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 還可以進行讀取和加載,默認會讀取本地當前運行目錄下的 .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
沒問題,成功讀取。
當然我們也可以自定義讀取的文件,如 .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ù)前綴進行分組處理,見下面的示例:
# 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)境變量傳進來,如果是危險命令,那么會非常危險。
所以在某些情況下我們需要驗證傳入的環(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)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用python爬取微博數(shù)據(jù)打造一顆“心”
這篇文章主要介紹了使用python基于微博數(shù)據(jù)打造一顆“心”,作為程序員,我準備了一份特別的禮物,用以往發(fā)的微博數(shù)據(jù)打造一顆“愛心”,我想她一定會感動得哭了吧,需要的朋友可以參考下2019-06-06
基于python實現(xiàn)簡單網(wǎng)頁服務(wù)器代碼實例
這篇文章主要介紹了基于python實現(xiàn)簡單網(wǎng)頁服務(wù)器代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-09-09
Python獲取網(wǎng)絡(luò)圖片和視頻的示例代碼
Python 是一種多用途語言,廣泛用于腳本編寫。我們可以編寫Python 腳本來自動化日常事務(wù)。本文將用Python實現(xiàn)獲取Google圖片和YouTube視頻,需要的可以參考一下2022-03-03

