本文授权转载自公众号:进击的Coder
在运行一个项目的时候,我们经常会遇到设置不同环境的需求,如设置是开发环境、测试环境还是生产环境,或者在某些设置里面可能还需要设置一些变量开关,如设置调试开关、日志开关、功能开关等等。
这些变量其实就是在项目运行时我们给项目设置的一些参数。这些参数一般情况来说,可以有两种设置方法,一种是通过命令行参数,一种是通过环境变量。二者的适用范围不同,在不同的场景下我们可以选用更方便的方式来实现参数的设置。
本节我们以 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 里的日志级别定义。
-
-
通过 list 方法可以对逗号分隔的内容进行 list 转化,并可以通过 subcast 方法对 list 的每个元素进行类型转化。
可以说有了这些方法,定义各种类型的变量都不再是问题了。
支持类型
总的来说,environs 支持的转化类型有这么多:
-
-
-
-
-
-
env.list
(accepts optional
subcast
keyword argument)
-
env.dict
(accepts optional
subcast
keyword argument)
-
-
-
-
env.timedelta
(assumes value is an integer in seconds)
-
-