Python 3最佳实践:区分生产环境和开发环境的配置

8
我正在开发一个嵌入式系统项目,我的dev设置与prod不同,包括变量和包的导入。
对于Python3应用程序,devprod之间配置文件的最佳结构方式是什么? prod: 我的设备使用pyserial与电子系统交换信息,并与服务器通信。 dev: 我使用来自函数的虚拟固定响应来模拟电子和服务器的响应。尽管这些函数在prod中很重要,但在dev中它们不那么重要。我可以模拟它们,因为此项目最重要的部分是使用和处理它们的函数。因此,在dev模式下,某些包的导入和函数调用是没有意义且会引入错误的。
每次我需要从一个模式切换到另一个模式时,都需要更改大量代码,有时还会引入错误。我知道这种方法并不是最佳实践,想知道有哪些最佳实践。
找到的最接近的解决方案: 这里有一种很好的解决方案,可以为每个环境设置不同的变量。我希望有类似的解决方案,可以在不同的环境中引入不同的包。
我的设置:
基本工作流程:
- 每秒执行一个task线程 - module_1执行工作并调用module_2 - module_2执行工作并调用module_3 - module_3执行工作并发送响应
基本文件夹结构:
- root - main - config.py - /config - prod - dev - /mod_1 - /mod_2 - /mod_3 - /replace_imports module_1module_3prod中各自使用特定的包,必须替换为dev函数。
# config.py
if os.environ["app"] == "dev":
    import * from root.config.dev
if os.environ["app"] == "prod":
    import * from root.config.prod

# config/prod.py
import _3rd_party_module_alpha
import _3rd_party_module_beta
...
obj_alpha = _3rd_party_module_alpha()
func_beta = _3rd_party_module_beta()

# config/dev.py
import * from root.replace_imports

# replace_imports.py
obj_alpha = fake_3rd_party_module_alpha()
func_beta = fake_3rd_party_module_beta()

我认为将配置作为代码导入已经是一个不好的想法。 - chepner
另外,应该是 from <module> import *,而不是 import * from <module>。(而且 from ... import * 也不被推荐使用。) - chepner
你的问题非常模糊。你能否澄清一下为什么需要导入不同的模块?或者为什么在生产和开发之间有如此明显的区别?你试图通过这种设置解决什么问题?通常情况下,人们可以交替使用生产和开发部分,甚至混合使用它们。而拥有两个不能混合使用的不同域表明生产和开发已经分化,这违背了它们存在的意义。 - MisterMiyagi
@MisterMiyagi我编辑了我的问题,尽可能添加了更多细节。“通常情况下,人们可以互换使用生产和开发部分”,这正是我决定在这里发布问题的原因。因为我没有找到解决我的问题的方法。而且我也相信我不是唯一一个遇到这种情况的人。“拥有两个不能混合的不同域表明prod和dev已经分化,这违背了它们存在的意义”,但在我的情况下并非如此。 - Kmelow
生产环境和非生产环境不匹配的明显原因是当您需要调用外部系统时。生产环境调用一个与实际业务功能相关联的URL。开发环境调用另一个用于某种测试但不会流入实际业务功能的URL。(例如,如果您在开发环境下下订单,则不会有任何交付。) - Lee Meador
3个回答

7
您真的不应该在从开发阶段到QA/CI再到生产阶段之间进行代码更改。当然,可以预期在不同阶段下您的开发和生产代码会有所不同,版本控制非常关键。但是,进入生产环境不应需要进行代码更改,只需进行配置更改即可。

环境变量(参见12因素应用程序)可以帮助解决问题,但有时配置信息包含在代码中,例如在Django设置文件中。

在像Django这样的环境中,“它指向”一个设置文件,我看到过这种情况:

base_settings.py:

common config

dev_settings.py:

#top of file
import * from base_settings


... dev specifics, including overrides of base...

编辑:我很清楚import *存在的问题。首先,这是一个特殊情况,用于配置,您希望导入所有内容。其次,import *的真正问题在于它会覆盖当前命名空间。该导入位于顶部,因此不会发生这种情况。抛开代码检查器,它们可以仅针对该行进行抑制,剩下的问题是,除非您查看基础内容,否则您可能不知道变量从哪里来。

prod_settings.py:

import * from base_settings

...production specifics, including overrides of base...

Webpack配置文件的高级用户(他们是残酷的)做同样的事情,即使用一个base.js,然后将其导入到dev.jsprod.js中。

关键在于尽可能多地使用base,可能需要借助环境变量的帮助(注意不要过度依赖它们,没有人喜欢有数十个环境变量设置的应用程序)。然后devprod基本上都是关于密钥、路径、URL、端口等内容。但是一定要确保将机密信息从中排除,因为它们自然而然地会聚集在那里,但没有必要在版本控制下。

关于您当前的代码

appname = os.getenv("app")

if appname == "dev":

    #outside of base=>dev/prod merges, at top of "in-config" namespaces, 
    #I would avoid `import *` here and elsewhere

    import root.config.dev as config

elif appname == "prod":

    import root.config.prod as config

else:
    raise ValueError(f"environment variable $app should have been either `dev` or `prod`. got:{appname}:")



最后,如果你没有类似于Django中的“它指向Python设置文件”的机制,可能可以通过将配置模块路径(xxx.prod,xxx.dev)存储在环境变量中,然后使用动态导入来自己实现。需要注意的是,你当前的代码已经基本实现了这一点,只是不能尝试/添加其他设置文件。
不要担心如果你一开始没有理解,也不要过度设计——找到最适合你/你的应用程序的方法需要一些时间。

1
通常最好避免使用 "import * from foo";它会给代码检查器带来麻烦。 - dstromberg
1
一般来说,是的。但是对于配置文件来说,这是一个特殊情况。当然,如果你想要固执己见,那就随你便吧。顺便说一下,可以通过指令来禁用代码检查工具。# pylint: disable=wildcard-import 然后 # pylint: enable=wildcard-import。而它被认为是反模式的真正原因不是代码检查工具,而是无意中覆盖命名空间引用。 - JL Peyret

4

Pipenv 是专门为这些事情和更多而创建的


Pipenv已经死了。 - rustyMagnet
1
检查我的答案发布以来的时间 - 已经过去了将近1.5年,一切都可能发生改变。 - Dominux
2
@rustyMagnet 你为什么说它已经死了?最后一次提交是在10天前。 - Hector Ordonez
@HectorOrdonez。你是正确的。 我可以看到有很多最近的提交。 我转移到了Poetry。但我不是唯一一个认为pipenv已经被淘汰的人:https://discuss.codecademy.com/t/pipenv-is-dead-what-does-everyone-here-use-python-environments/595543 - rustyMagnet
@rustyMagnet 感谢分享。Poetry 似乎得到了很好的照顾,我可能会尝试一下。 - Hector Ordonez
rustyMagenet已经死了。 - Marwen Trabelsi

3

使用Git分支(或Mercurial或您正在使用的任何版本控制系统 - 您正在使用版本控制系统,对吗?)和虚拟环境。这正是它们的用途。您的配置文件应仅用于诸如数据库连接标识符,API密钥等内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接