大多数计算机应用程序都可以使用配置来指定行为,无论是通过命令行标志、环境变量还是配置文件。作为一名软件开发人员,处理配置时会遇到一些挑战,例如解析不合法的输入、验证它以及在程序的任意位置访问它。以Python为例,在这篇博客中,我想分享一些可以帮助您安全有效地处理配置的最佳实践,并且我希望和您达成共识:这些是在您自己的代码中应该遵循的合理原则。
介绍
除了最简单的程序外,所有的程序都有一组参数来控制它们的行为。作为具体的例子,考虑ls工具的输出格式、nginx监听的端口或git在提交消息中使用的电子邮件地址。根据应用程序的大小和复杂性,可能有许多这样的参数,它们可能只影响一个小的执行细节或者整个程序行为。当您处理配置时,有很多方面需要考虑:首先,它是如何从外部传递到您的程序中的,如何解析和验证?其次,如何在程序内部处理、访问和在组件之间传递?根据应用程序的类型,您必须考虑在程序运行时用户如何检查和更新它。从操作的角度来看,您可能必须考虑如何管理、测试多个配置并将其部署到生产环境中。每一个主题都可能变得相当复杂,值得深入探讨。不过,在这篇博文中,我只想关注第二个方面。我将介绍一些处理程序内部配置的指导原则,这些原则是经过时间检验的,我想推荐给任何开发中小型应用程序的人。在过去,我用各种编程语言(如Go、Scala和Python)构建和维护应用程序。在这篇博文中,我想以Python为例,因为它的动态特性允许使用很多机制用以提高开发速度和灵活性(例如,在运行时修改类),但从长远来看可能会使维护和重构更加困难。一个简单的例子当谈到软件应该如何工作以及组件应该如何交互的重要思想时,有时很难与实际编码联系起来。为了避免出现这种情况,让我们跳到下面的代码示例中,看看我想在本文中解决的一些问题:

在评论中,我已经给出了一些关于该代码可能存在的缺点,但是让我们现在更详细地探讨一下。指导原则编程是一项在智力上具有挑战性的任务,因此我认为作为软件工程师,我们应该将尽可能多的复杂任务委托给我们的工具,如ide、linter、格式化程序、编译器或类型检查程序。如果可以使用一个工具来发现错误和提高代码质量,那么我认为这就证明了用这种工具来编写代码是合理的。另外,如果尽管我们仔细检查和使用了工具,但代码中仍有错误,那么应该在应用程序启动时尽快报告,这会产生一个重要的警告消息,并且在许多情况下,程序会立即退出。最糟糕的事情莫过于在一次看似成功的部署的几个小时后,半夜里发现某个配置密钥丢失。基于这些基础,我认为处理应用程序内部配置的数据结构应该遵循以下四个原则:

  • 它应该使用标识符而不是字符串键来访问配置值。

  • 它的值应该是静态类型的。

  • 应该尽早验证。

  • 它应该声明在它使用的地方。

让我在下面解释这些原则及其作用。一.使用标识符而不是字符串键值可能与近年来文件交换和序列化格式的某种“JSONification”有关,以PEP 484为标准的字符串键词典Dict[str,Any]似乎已经成为许多Python开发人员的一站式数据结构。很简单,只需使用json.loads()处理一个json格式的字符串后放入Python字典,然后使用像config[“port”]或config[“user”][“email”]一样的代码随意访问它,就像我在介绍性示例中所做的那样。(这种方法不是Python独有的,例如Scala的Lightbend配置库也有一个类似conf.getInt(“foo.bar”)的API。)如果需要新的配置条目,只需将其添加到JSON文件中,并在整个代码中立即使用它。但是,这种方法有许多缺点:

  • 无法检测不一致的拼写,例如,密钥是“user”还是“users”。

  • 如果存在不一致,不能明确哪里发生了错误。

    只有和字典中的值相同才是正确的。

  • 在实际访问数据之前,不会发现丢失的数据。

  • 无法使用IDE/工具来重命名密钥,需要找到并替换字符串的所有匹配项。

  • 不能使用检查变量名格式一致性的工具。

  • 重复的字符串解析和字典查找相当费力。

因此,我建议使用标识符,而不是使用字符串键(在字典中或作为某些get()方法的参数)。直接的方法是使用类成员,然后编写config.user.email,而不是config[“user”][“email”]。请注意,Python的数据类(在3.7版中引入,但在3.6版中通过dataclasses模块提供)对于保存此类数据非常方便。这样做可以解决上面列出的问题:

  • 在编译语言中,编译器显然会立即告诉您是否存在拼写错误,但对于Python,一个足够现代的IDE通常会指出是否使用了未声明的变量或类成员。

  • 类中定义中的名称才是唯一确定的正确名称。

  • 即使在Python中,声明的变量也可能没有初始化(参见PEP 526),但在许多情况下,IDE或linter会告诉您这一点。

  • 使用IDE可以轻松完成重命名。

  • 可以应用普通格式化程序或样式检查程序。

二.静态类型在上一节中,我们看到了Dict[str,Any]的str部分是如何导致问题的,现在让我们来看看Any部分。我不想在这里讨论静态类型编程语言和动态类型编程语言的所有方面,但就程序正确性而言,有一些证据表明,静态类型检查减少了修复错误时的工作量并且效果更好。在Python中,mypy可以对使用类型注释的代码执行此类检查。我想鼓励您在代码中使用这些注释,而不仅仅是在使用配置时。从上面的一个例子来看,start_server(port=os.environ.get(“port”,80)),对于需要整数值端口的函数,如果设置了环境变量port,则此代码将失败,因为os.environ的条目始终是字符串类型。您可能知道这一点,但如果start_server()函数声明为类似start_server(port:int),那么使用mypy进行的检查将显示出问题:py:6:错误:“start_server”的参数1具有不兼容的类型“Union[str,int];应为“int”除了这些基本检查之外,静态类型还提供了一种优雅的方法来限制代码可能接受的输入集。例如,当您有一个引用文件的配置项时,请使用pathlib.Path而不是str,并避免处理字符串格式的无效文件名。如果有固定数量的可选值,请使用enum.enum来表示它。如果只能指定一个或另一个值,请使用Union。如果值是可选的,则通过使用optional显式表达。通过使用类型系统正式指定允许或禁止的值,您可以使用工具来发现您没有覆盖的代码路径,或者那些实际上永远不会覆盖的代码路径。还有一件事要考虑,特别是在处理诸如持续时间、重量、距离、速度等物理维度时,要抽象出维度,而不是具体的单位。例如,与其像check_interval_s:float或check_interval_ms:int那样声明配置项,不如像check_interval:datetime.timedelta那样声明它。然后,您可以根据这些维度编写大部分代码,在抽象级别上使用它们进行计算,并且只在使用外部库时(例如调用time.sleep(check_interval.total_seconds())将它们转换为具体值。最后要注意:在Python中,类型注释在运行时没有验证效果。即使所有代码都被注释并通过类型检查,如果变量a:int在运行时是一个字符串,那么意外的事情也会发生。下一节的主题是确保实际数据看起来符合您的预期。

三.早期验证

对于大多数配置值,拥有一个特定的格式、类型或数据范围才是有意义的。如前一节所述使用静态类型已经是限定值必须要有某种格式的示例。可能还有其他约束,如最小值和最大值,与某个正则表达式匹配,或指向配置的另一个(已存在)部分。一种简单的方法是在使用配置的位置执行验证。例如,你可以写
在使用这些值时也类似。

然而,这会导致一些问题:

  • 必须在使用该值的每个位置验证该值,从而导致代码重复。或者,您在使用它时需要记住是否已经验证了它。

  • 如果有问题,那么只有在第一次访问配置值时才会出现问题。这使得发现错误更加困难,并且需要更多的力气来检查新的配置值是否实际有效。

如上所述,在Python中,即使在类声明中声明port:int,config.port在运行时也可以是一个字符串。你绝对不想在每次使用该值时都去检查。

因此,我建议在程序启动后尽快验证配置,如果发现配置无效,请立即退出。注意,如果您选择使用上一节中建议的适当类型来表示配置条目,那么在许多情况下,只要能成功地解析配置就能保证配置有效(参见解析,不要验证)。

在操作方面,早期验证确保程序在启动后的一段时间内不会因为配置无效而退出。从开发的角度看,它使工作变得更容易,因为您可以在任何地方假设配置数据只包含有效值,并且可以像使用程序中的任何其他对象一样安全地使用配置。四.在使用配置的地方声明它最后一个原则是,配置项应该声明在它们使用的地方附近,例如,作为使用它的代码所在模块中的一个类。此规则不能直接从上述基础派生,因为它不一定有助于更有效地使用工具,也不一定有助于及早预防或报告错误。但是,与在一个地方声明所有配置条目相比,它在软件工程方面有两个优点:物理封闭性有助于导航,例如,更容易找到使用某个配置项的位置。此外,如果您使用的数据结构还定义了配置值的有效边界,那么在接近依赖这些边界的代码旁定义配置是有意义的。它有助于避免在不同的、不相关的组件中使用相同的配置项。假设您有一个条目,例如timeout,它定义在一个公共位置,并且可以从所有模块访问,那么很容易会去在不同的不相关位置重用同一个timeout条目,而不是添加一个新条目并适当地命名和记录它。和在模块中本地定义配置对比下,则更容易看出这样做不好,例如,您很可能不会在db.backend模块中导入web.http.config.client.timeout以将其用作数据库连接池的设置。在测试以配置为参数的组件时,只需要为组件使用的条目创建模拟配置对象,而不需要为整个应用程序模拟完整的配置。

每个模块的子配置可以通过组合或继承组装成一个更大的类。一般来说,我建议组合,因为从多个小配置类继承可能在某个点上导致命名冲突。

把碎片拼在一起所以让我们看看如何将这些原则组合成一个小的代码示例。这个例子深受Alexandru Nedelcu的Scala最佳实践集合第3.5节所述方法的启发。我们有三个模块,每个模块都定义了类型良好的配置类。(为了简洁起见,我省略了import语句。)

例如,app.user模块中的类可以在构造函数中获取其本地配置类的实例并使用它,而不必担心类型不匹配或缺少值。用户模块中的单元测试不必模拟整个应用程序配置。注意,数据类特别适合这个应用程序,因为它们不能声明成员而不初始化,这与普通的Python类相反。如果在dataclass声明中添加了一个成员,那么mypy将报告代码中在没有为新成员提供值的情况下构造实例的所有位置。然后,位于不同模块中的主程序可以定义一个应用程序范围的配置类,如下所示:到目前为止,我还没有讨论如何实际创建实例并对这个全局配置类执行验证。对于类似这样的简单情况,将字典转换为数据类的dacite库非常有用。请考虑以下代码:

如果执行此代码时没有异常,那么我们就有了一个有效的配置对象,如 我希望可以和您达成共识,上述都是传递配置数据的更好方法,而不仅仅是一个包含已解析的JSON内容的字典。

英文原文:https://tech.preferred.jp/en/blog/working-with-configuration-in-python/ 译者:阿布铥

db2 参数标识符使用无效_在Python应用程序中使用配置的最佳实践相关推荐

  1. python编辑器和终端_从python curses程序运行终端文本编辑器

    我想在python curses程序中使用外部终端文本编辑器和寻呼机.我使用子进程库.在大多数情况下,它工作得很好,除了当我退出文本编辑器时(与nemo和vi相同),我不能再次使光标不可见.另外,在调 ...

  2. python语言包含的错误,Python语言程序中包含的错误,一般分为三种,以下____________不是其中的一种...

    Python语言程序中包含的错误,一般分为三种,以下____________不是其中的一种 答:编译错误 人体体温能自动调控在37度,其原因是( ). 答:人体内产生的热能是分批放出的 人体内有完善的 ...

  3. 在Qt for Python应用程序中使用Designer UI文件

    在Qt for Python应用程序中使用Designer UI文件 在Qt for Python应用程序中使用Designer UI文件 将表单转换为Python代码 UiTools方法 在Qt f ...

  4. python使用函数的目的_在Python 3.x中经常看到定义函数有一个单独的 * 参数?定义这样参数的目的是?怎样对其取值呢?...

    参数在python中总是通过赋值进行传递的.在默认情况下,参数是通过其位置进行匹配的,从左到右,而且必须精确的传递和函数头部参数名一样多的参数. 这种默认的传递方式很简单 def f(a,b,c): ...

  5. python3 二进制文件比较_《Python 3程序开发指南(第2版•修订版)》——7.4 随机存取二进制文件...

    本节书摘来自异步社区<Python 3程序开发指南(第2版•修订版)>一书中的第7章,第7.4节,作者[英]Mark Summerfield,王弘博,孙传庆 译,更多章节内容可以访问云栖社 ...

  6. python怎么做软件程序_看 Python 超级程序员使用什么开发工具

    Python超级程序员使用的开发工具 我以个人的身份采访了几个顶尖的Python程序员,问了他们以下5个简单的问题: 当前你的主要开发任务是什么? 你在项目中使用的电脑是怎样的? 你使用什么IDE开发 ...

  7. python类的属性和对象属性_在python的类中动态添加属性与生成对象

    本文将通过一下几个方面来一一进行解决 1.程序的主要功能 2.实现过程 3.类的定义 4.用生成器generator动态更新每个对象并返回对象 5.使用strip 去除不必要的字符 6.rematch ...

  8. python中函数的调用_慢步python,编程中函数的概念,python中函数的声明和调用

    函数,曾经是一个很高大尚的概念.笔者是在高中数学里认识的函数,先是从y=2x+3 这条代数式开始的.y是因变量,x是自变量,y因为x取值的变化而变化. 再后来式子变成这样:f(x)=2x+3,f(x) ...

  9. python 替换array中的值_利用Python提取视频中的字幕(文字识别)

    我的CSDN博客id:qq_39783601,昵称是糖潮丽子~辣丽 从今天开始我会陆续将数据分析师相关的知识点分享在这里,包括Python.机器学习.数据库等等. 今天来分享一个Python小项目! ...

最新文章

  1. 多线程导出大规模excel文件
  2. 怎么更新鸿蒙系统mate10,能不能升级鸿蒙系统?
  3. 响应json数据之过滤静态资源
  4. opengl 流程梳理
  5. 外联接、自联接与联合
  6. CSV出力ボタンラッパー(asp.net)[イベントの作り方に役立つ]
  7. NLP《词汇表示方法(三)word2vec》
  8. Android学习--广播机制
  9. 最全数据指标体系集合!覆盖9个行业4个业务场景,全是干货
  10. NLP论文阅读1--More Data, More Relations, More Context and More Openness: A Review and Outlook for Relati
  11. CS下载、安装以及简单使用
  12. Go + Redis 实现分布式锁
  13. zip压缩文件处理方案(Zip4j压缩和解压)
  14. 如何使用aria2及webui-aria2下载百度云资源
  15. 高通msm8953平台I2C分析
  16. 网上搜索电子书的办法
  17. [从头读历史] 第265节 诗经 周南
  18. mysql获取当天每小时统计_详解mysql 获取某个时间段每一天、每一个小时的统计数据...
  19. GB/T 25000.51-2016解读系列之易用性
  20. PCA(主成分分析)及源码

热门文章

  1. C++ 字符串编程训练1
  2. [转] OpenStack Kilo 更新日志
  3. CSS3 实现厉害的文字和输入框组合效果
  4. MVC系统学习6—Filter
  5. linux top p 乱码,将Linux top命令输入到指定文件时的乱码问题
  6. 通过javascript改变form提交的action,实现不同的按钮向不同的action提交同一个form的数据
  7. VSC为_计及功率控制模式的VSC-MTDC交直流并列运行系统概率潮流计算
  8. Mongo服务器二进制文件修复,Mongodb-File-Server
  9. 五、Git多人开发:同时变更了文件名和文件内容如何处理?
  10. 九十七、轻松搞定Python中的PDF办公自动化系列