溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Python 使用 environs 庫定義環(huán)境變量的方法

發(fā)布時間:2020-10-05 21:48:58 來源:腳本之家 閱讀:191 作者:崔慶才 欄目:開發(fā)技術(shù)

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)它提供了這些功能:

  • 通過 env 可以設(shè)置必需定義的變量,如果沒有定義,則會報錯。
  • 通過 date、timedelta 方法可以對日期或時間進(jìn)行轉(zhuǎn)化,轉(zhuǎn)成 datetime.date 或 timedelta 類型。
  • 通過 log_level 方法可以對日志級別進(jìn)行轉(zhuǎn)化,轉(zhuǎn)成 logging 里的日志級別定義。
  • 通過 bool 方法可以對布爾類型變量進(jìn)行轉(zhuǎn)化。
  • 通過 list 方法可以對逗號分隔的內(nèi)容進(jìn)行 list 轉(zhuǎn)化,并可以通過 subcast 方法對 list 的每個元素進(jìn)行類型轉(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 個人認(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)文章希望大家以后多多支持億速云!

向AI問一下細(xì)節(jié)

免責(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)容。

AI