先打个比方同一款游戏通常会有好几个“服”开发者自己电脑上的本地服内部人员试玩的测试服玩家正在玩的正式服这几个服运行的是同一套游戏程序表现却不一样测试服可以开作弊指令正式服不行正式服周末有双倍经验测试服没有它们连接的数据库也不同。环境变量就是贴在每台服务器上的“设置便签”。程序启动时先看一眼便签再决定怎么运行。程序本身不用改换一张便签行为就变了。下面用一个虚构的小游戏《勇者打怪》从头演示一遍。第一阶段不用环境变量问题很多开发者小明写了第一版代码把所有设置都直接写在代码里# game.py第一版硬编码SERVER_NAME本地测试服EXP_RATE1# 经验倍率DEBUGTrue# 调试模式显示内部数据允许作弊指令DB_PASSWORDxiaoming123# 数据库密码defkill_monster(player,monster,base_exp):expint(base_exp*EXP_RATE)print(f{player}击败了{monster}获得{exp}点经验)很快问题就出现了。问题 1每上一个服都要改代码部署正式服前小明得把SERVER_NAME改成“正式一服”把DEBUG改成False。有一次他忘了关调试模式玩家发现可以用作弊指令刷装备游戏经济崩了。问题 2周末做活动也要改代码运营说“这周末开双倍经验。”小明只好把EXP_RATE 1改成2重新打包、上传、发布。周一再改回1又发布一次。问题 3密码泄露小明把代码传到了 GitHubDB_PASSWORD xiaoming123也一起被传了上去。有人看到后登录了数据库删光了玩家存档。第二阶段改用环境变量小明改了一下代码不在代码里写具体的值而是向运行环境“要”这些值。# game.py第二版使用环境变量importos SERVER_NAMEos.getenv(SERVER_NAME,本地测试服)# 没设置时默认为“本地测试服”EXP_RATEfloat(os.getenv(EXP_RATE,1))# 默认 1 倍经验DEBUGos.getenv(DEBUG,false).lower()true# 默认关闭调试MAX_PLAYERSint(os.getenv(MAX_PLAYERS,10))# 默认最多 10 人defkill_monster(player,monster,base_exp):expint(base_exp*EXP_RATE)print(f{player}击败了{monster}获得{exp}点经验)ifDEBUG:print(f [调试] 基础经验{base_exp}倍率{EXP_RATE})print(f欢迎来到【{SERVER_NAME}】最多{MAX_PLAYERS}人同时在线)kill_monster(勇者小明,史莱姆,100)os.getenv(EXP_RATE, 1)的意思是去环境里找一个叫EXP_RATE的设置找不到就使用默认值1。动手试一试建议亲自运行把上面的代码保存为game.py然后在终端中运行。① 什么都不设置直接运行python game.py欢迎来到【本地测试服】最多 10 人同时在线 勇者小明 击败了 史莱姆获得 100 点经验所有设置都使用默认值。② 模拟正式服的周末双倍经验# Linux / macOSSERVER_NAME正式一服EXP_RATE2MAX_PLAYERS5000python game.py# Windows PowerShell$env:SERVER_NAME正式一服;$env:EXP_RATE2;$env:MAX_PLAYERS5000;python game.py欢迎来到【正式一服】最多 5000 人同时在线 勇者小明 击败了 史莱姆获得 200 点经验代码一个字都没改经验就翻倍了。③ 打开调试模式查问题DEBUGtrue python game.py欢迎来到【本地测试服】最多 10 人同时在线 勇者小明 击败了 史莱姆获得 100 点经验 [调试] 基础经验100倍率1.0到这里你应该能感受到它的作用了程序是同一个运行前给的“便签”不同结果就不同。第三阶段实际项目中怎么管理这些“便签”每次都在命令行里输入一长串设置太麻烦实际项目中一般会把它们写进.env文件。每台服务器上放一份内容不同的.env小明的电脑本地服SERVER_NAME本地测试服EXP_RATE1DEBUGtrueMAX_PLAYERS10DB_PASSWORDlocal_pass测试服服务器SERVER_NAME内部测试服EXP_RATE10# 测试人员升级快一点方便测试高级内容DEBUGtrueMAX_PLAYERS50DB_PASSWORDtest_xxx正式服服务器SERVER_NAME正式一服EXP_RATE1DEBUGfalse# 正式服必须关闭MAX_PLAYERS5000DB_PASSWORDProd_8f$k2!Qz# 只有运维知道不在代码里在代码开头加两行让程序自动读取.env文件# 先安装pip install python-dotenvfromdotenvimportload_dotenv load_dotenv()# 读取当前目录下的 .env 文件同时在.gitignore中写上.env这样密码文件就不会被上传到 GitHub。回头看之前的三个问题都解决了场景以前硬编码现在环境变量上线正式服改代码容易忘记关闭调试代码不动正式服的.env里本来就是DEBUGfalse周末双倍经验改代码 → 打包 → 发布周一再来一遍运维把EXP_RATE改成2重启服务器即可数据库密码写在代码里跟着代码泄露只存在于服务器上代码仓库里没有新开“正式二服”复制一份代码再修改同一个程序换一份.env这就是环境变量的好处一套代码到处运行本地服、测试服、正式服用的是同一个程序。改设置不改代码运营活动、调整人数上限都不需要开发者参与。密码不进代码降低泄露风险。不容易出错每台服务器的设置都是固定的不会因为“忘了改回来”而出事故。但它也有坏处同样用游戏举例坏处 1写错名字程序不会提醒你运维把EXP_RATE2误写成EXP_RTAE2。程序找不到EXP_RATE就静默使用默认值1。周末的双倍经验活动没有生效玩家纷纷投诉却没有任何报错。对策重要的设置不要给默认值。缺失时让程序在启动阶段直接报错宁可启动失败也不要悄悄出错。DB_PASSWORDos.environ[DB_PASSWORD]# 没有设置就会立即报错坏处 2读出来的都是字符串环境变量里只有文字。写DEBUGfalse程序拿到的是字符串false如果写成bool(false)结果反而是True调试模式被意外打开。所以代码里要手动转换类型比如上面的 true、float(...)、int(...)。坏处 3修改后必须重启修改EXP_RATE后必须重启服务器才会生效在线玩家会掉线。所以一般选择在停服维护时修改。如果需要“不停服、实时调整”就要用配置中心或后台管理系统。坏处 4不能放进游戏客户端DB_PASSWORD只能放在服务器上。如果放进玩家下载的客户端或网页游戏的前端代码里玩家拆包就能看到。坏处 5不适合复杂数据怪物属性表、装备掉落表这类成百上千行的数据不适合放在环境变量里应该放在配置表或数据库中。环境变量只适合放少量开关和关键参数。一个真实的例子《我的世界》服务器很多人使用 Docker 镜像itzg/minecraft-server搭建《我的世界》私服它就是通过环境变量来配置的dockerrun-d-p25565:25565\-eEULATRUE\-eDIFFICULTYhard\-eMODEsurvival\-eMAX_PLAYERS20\itzg/minecraft-serverEULATRUE同意用户协议DIFFICULTYhard困难模式MODEsurvival生存模式MAX_PLAYERS20最多 20 人想换成创造模式、和平难度只需要改成-e MODEcreative -e DIFFICULTYpeaceful再重新启动。服务器程序完全相同只是“便签”不一样。这正是环境变量最典型的用法。一句话总结代码负责“怎么玩”环境变量负责“这台服务器上具体怎么设置”。程序启动时读取环境变量所以同一个游戏程序可以变成本地服、测试服、正式服或活动服而且不用改一行代码。