Python 使用 environs 库定义环境变量的方法


Posted in Python onFebruary 25, 2020

Environs是解析环境变量的Python库。它的开发受envparse启发,底层使用marshmallow验证并序列化值。

在运行一个项目的时候,我们经常会遇到设置不同环境的需求,如设置是开发环境、测试环境还是生产环境,或者在某些设置里面可能还需要设置一些变量开关,如设置调试开关、日志开关、功能开关等等。

这些变量其实就是在项目运行时我们给项目设置的一些参数。这些参数一般情况来说,可以有两种设置方法,一种是通过命令行参数,一种是通过环境变量。二者的适用范围不同,在不同的场景下我们可以选用更方便的方式来实现参数的设置。

本节我们以 Python 项目为例,说说环境变量的设置。

设置和获取环境变量

首先,我们先来了解一下在 Python 项目里面怎样设置和获取变量。

首先让我们定义一个最简单的 Python 文件,命名为 main.py,内容如下:

import os
print(os.environ['VAR1'])

在这里我们导入了 os 模块,它的 environ 对象里面就包含了当前运行状态下的所有环境变量,它其实是一个 os._Environ 对象,我们可以通过类似字典取值的方式从中获取里面包含的环境变量的值,如代码所示。

好,接下来我们什么也不设置,直接运行,看下结果:

python3 main.py

结果如下:

raise KeyError(key) from None
KeyError: 'VAR1'

直接抛出来了一个错误,这很正常,我们此时并没有设置 VAR1 这个环境变量,当然会抛出键值异常的错误了。

接下来我们在命令行下进行设置,运行如下命令:

VAR1=germey python3 main.py

运行结果如下:

germey

可以看到我们在运行之前,在命令行之前通过键值对的形式对环境变量进行设置,程序就可以获取到 VAR1 这个值了,成功打印出来了 germey。

但这个环境变量是永久的吗?我们这次再运行一遍原来的命令:

python3 main.py

结果如下:

raise KeyError(key) from None
KeyError: 'VAR1'

嗯,又抛错了。

这说明了什么,在命令行的前面加上的这个环境变量声明只能对当前执行的命令生效。

好,那既然如此,我难道每次运行都要在命令行前面加上这些声明吗?那岂不麻烦死了。

当然有解决方法,我们使用 export 就可以了。

比如这里,我们执行如下命令:

export VAR1=germey

执行完这个命令之后,当前运行环境下 VAR1 就被设置成功了,下面我们运行的命令都能获取到 VAR1 这个环境变量了。

下面来试试,还是执行原来的命令:

python3 main.py

结果如下:

germey

可以,成功获取到了 VAR1 这个变量,后面我们运行的每一个命令就都会生效了。

但等一下,这个用了 export 就是永久生效了吗?

其实并不是,其实这个 export 只对当前的命令行运行环境生效,我们只要把命令行关掉再重新打开,之前用 export 设置的环境变量就都没有了。

可以试试,重新打开命令行,再次执行原来的命令,就会又抛出键值异常的错误了。

那又有同学会问了,我要在每次命令行运行时都想自动设置好环境变量怎么办呢?

这个就更好办了,只需要把 export 的这些命令加入到 ~/.bashrc 文件里面就好了,每次打开命令行的时候,系统都会自动先执行以下这个脚本里面的命令,这样环境变量就设置成功了。当然这里面还有很多不同的文件,如 ~/.bash_profile 、~/.zshrc 、~/.profile、/etc/profile 等等,其加载是有先后顺序的,大家感兴趣可以去了解下。

好了,扯远了,我们现在已经了解了如何设置环境变量和基本的环境变量获取方法了。

更安全的获取方式

但是上面的这种获取变量的方式实际上是非常不友好的,万一这个环境变量没设置好,那岂不是就报错了,这是很不安全的。

所以,下面再介绍几种比较友好的获取环境变量的方式,即使没有设置过,也不会报错。

我们可以把中括号取值的方式改成 get 方法,如下所示:

import os
print(os.environ.get('VAR1'))

这样就不会报错了,如果 VAR1 没设置,会直接返回 None,而不是直接报错。

另外我们也可以给 get 方法传入第二个参数,表示默认值,如下所示:

import os
print(os.environ.get('VAR1', 'germey'))

这样即使我们如果设置过 VAR1,他就会用 germey 这个字符串代替,这就完成了默认环境变量的设置。

下面还有几种获取环境变量的方式,总结如下:

import os
print(os.getenv('VAR1', 'germey'))

这个方式比上面的写法更简单,功能完全一致。

弊端

但其实上面的方法有一个不方便的地方,如果我们想要设置非字符串类型的环境变量怎么办呢?比如设置 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'))

然后设置环境变量的时候就变成这样子:

export VAR1=1
export VAR2=2.3
export VAR3='["1", "2"]'

这样才能成功获取到结果,打印出来结果如下:

1
2.3
['1', '2']

不过看下这个,写法也太奇葩了吧,又是类型转换,又是 json 解析什么的,有没有更好的方法来设置。

environs

当然有的,下面推荐一个 environs 库,利用它我们可以轻松地设置各种类型的环境变量。

这是一个第三方库,可以通过 pip 来安装:

pip3 install environs

好,安装之后,我们再来体验一下使用 environs 来设置环境变量的方式。

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 等方法,我们就不用再去进行类型转换了。

与此同时,设置环境变量的方式也有所变化:

export VAR1=1
export VAR2=2.3
export VAR3=1,2

这里 VAR3 是列表,我们可以直接用逗号分隔开来。

打印结果如下:

1
2.3
['1', '2']

官方示例

下面我们再看一个官方示例,这里示例了一些常见的用法。

首先我们来定义一些环境变量,如下:

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

这里有字符串、有日期、有日志级别、有字符串列表、有浮点数列表、有布尔。

我们来看下怎么获取,写法如下:

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]

通过观察代码可以发现它提供了这些功能:

  • 通过 env 可以设置必需定义的变量,如果没有定义,则会报错。
  • 通过 date、timedelta 方法可以对日期或时间进行转化,转成 datetime.date 或 timedelta 类型。
  • 通过 log_level 方法可以对日志级别进行转化,转成 logging 里的日志级别定义。
  • 通过 bool 方法可以对布尔类型变量进行转化。
  • 通过 list 方法可以对逗号分隔的内容进行 list 转化,并可以通过 subcast 方法对 list 的每个元素进行类型转化。

可以说有了这些方法,定义各种类型的变量都不再是问题了。

支持类型

总的来说,environs 支持的转化类型有这么多:

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 方法可以对元素内容进行转化。

对于 dict、url、date、uuid、path 这里我们来补充说明一下。

下面我们定义这些类型的环境变量:

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)

运行结果如下:

<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

可以看到,它分别给我们转化成了 dict、dict、ParseResult、UUID、PosixPath 类型了。

在代码中直接使用即可。

文件读取

如果我们的一些环境变量是定义在文件中的,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 文件中写入如下内容:

APP_DEBUG=false
APP_ENV=prod

运行结果如下:

False
prod

没问题,成功读取。

当然我们也可以自定义读取的文件,如 .env.test 文件,内容如下:

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 传入了定义环境变量的文件路径即可。

前缀处理

environs 还支持前缀处理,一般来说我们定义一些环境变量,如数据库的连接,可能有 host、port、password 等,但在定义环境变量的时候往往会加上对应的前缀,如 MYSQL_HOST、MYSQL_PORT、MYSQL_PASSWORD 等,但在解析时,我们可以根据前缀进行分组处理,见下面的示例:

# 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 方法组合使用即可实现分区处理,这样在每个分组下再赋值到一个字典里面即可。

合法性验证

有些环境变量的传入是不可预知的,如果传入一些非法的环境变量很可能导致一些难以预料的问题。比如说一些可执行的命令,通过环境变量传进来,如果是危险命令,那么会非常危险。

所以在某些情况下我们需要验证传入的环境变量的有效性,看下面的例子:

# 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.']

关于 marshmallow 库的用法,大家可以参考:https://marshmallow.readthedocs.io/en/stable/,后面我也抽空写一下介绍下。

最后再附一点我平时定义环境变量的一些常见写法,如:

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')

这里定义了一些开发环境、日志路径、数据库连接、API 设置、开关设置等等,是从我之前写的一个代理池项目拿来的,大家可以参考:https://github.com/Python3WebSpider/ProxyPool。

总结

到此这篇关于Python 使用 environs 库来更好地定义环境变量的文章就介绍到这了,更多相关python 使用 environs 库定义环境变量内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
Python中为什么要用self探讨
Apr 14 Python
Python内置数据结构与操作符的练习题集锦
Jul 01 Python
基于Python的接口测试框架实例
Nov 04 Python
python PrettyTable模块的安装与简单应用
Jan 11 Python
pow在python中的含义及用法
Jul 11 Python
python遍历文件目录、批量处理同类文件
Aug 31 Python
python字符串判断密码强弱
Mar 18 Python
简单了解Java Netty Reactor三种线程模型
Apr 26 Python
python框架flask入门之环境搭建及开启调试
Jun 07 Python
Python Tkinter图形工具使用方法及实例解析
Jun 15 Python
基于Python爬取京东双十一商品价格曲线
Oct 23 Python
python解压zip包中文乱码解决方法
Nov 27 Python
Python 序列化和反序列化库 MarshMallow 的用法实例代码
Feb 25 #Python
python用pip install时安装失败的一系列问题及解决方法
Feb 24 #Python
python numpy--数组的组合和分割实例
Feb 24 #Python
python梯度下降算法的实现
Feb 24 #Python
利用python实现逐步回归
Feb 24 #Python
python数据分析:关键字提取方式
Feb 24 #Python
python数据预处理 :数据共线性处理详解
Feb 24 #Python
You might like
用PHP将数据导入到Foxmail
2006/10/09 PHP
PHP FATAL ERROR: CALL TO UNDEFINED FUNCTION BCMUL()解决办法
2014/05/04 PHP
javascript 仿QQ滑动菜单效果代码
2010/09/03 Javascript
基于JQuery的简单实现折叠菜单代码
2010/09/15 Javascript
js 验证身份证信息有效性
2014/03/28 Javascript
Node.js安装教程和NPM包管理器使用详解
2014/08/16 Javascript
jQuery实现平滑滚动到指定锚点的方法
2015/03/20 Javascript
jquery实现select选择框内容左右移动代码分享
2015/11/21 Javascript
基于Javascript实现倒计时功能
2016/02/22 Javascript
巧用Javascript的逻辑运算符
2016/12/02 Javascript
Bootstrap的modal拖动效果
2016/12/25 Javascript
angular中的http拦截器Interceptors的实现
2017/02/21 Javascript
javascript计算渐变颜色的实例
2017/09/22 Javascript
node跨域转发 express+http-proxy-middleware的使用
2018/05/31 Javascript
react 应用多入口配置及实践总结
2018/10/17 Javascript
[38:23]完美世界DOTA2联赛循环赛 FTD vs PXG BO2第二场 11.01
2020/11/02 DOTA
Python修改Excel数据的实例代码
2013/11/01 Python
Python下singleton模式的实现方法
2014/07/16 Python
Python教程之全局变量用法
2016/06/27 Python
python爬虫headers设置后无效的解决方法
2017/10/21 Python
django rest framework之请求与响应(详解)
2017/11/06 Python
详解Python核心编程中的浅拷贝与深拷贝
2018/01/07 Python
django中模板的html自动转意方法
2018/05/27 Python
基于python绘制科赫雪花
2018/06/22 Python
Appium+python自动化怎么查看程序所占端口号和IP
2019/06/14 Python
Python项目跨域问题解决方案
2020/06/22 Python
python 对一幅灰度图像进行直方图均衡化
2020/10/27 Python
皮姆斯勒语言学习:Pimsleur Language Programs
2018/06/30 全球购物
请写一个C函数,若处理器是Big_endian的,则返回0;若是Little_endian的,则返回1
2015/07/16 面试题
致标枪运动员广播稿
2014/02/06 职场文书
企业管理标语
2014/06/10 职场文书
幼儿园老师个人总结
2015/02/28 职场文书
你知道Java Spring的两种事务吗
2022/03/16 Java/Android
MySQL派生表联表查询实战过程
2022/03/20 MySQL
海康机器人重磅发布全新算法开发平台VM4.2
2022/04/21 数码科技
了解MySQL查询语句执行过程(5大组件)
2022/08/14 MySQL