使用Python文件作为配置文件有多么糟糕的主意?


72

我一直使用JSON文件配置应用程序。我从编写大量Java时就开始使用它们,现在我主要从事服务器端和数据科学Python开发,并且不确定JSON是否是正确的选择。

我已经看到Celery使用实际的Python文件进行配置。最初我对此持怀疑态度。但是,使用简单的Python数据结构进行配置的想法开始对我产生影响。一些优点:

  • 数据结构将与我通常在其中进行编码的结构相同。因此,我无需改变心态。
  • 我的IDE(PyCharm)了解配置和代码之间的联系。Ctrl+ B使您可以轻松地在配置和代码之间跳转。
  • 我不需要使用IMO不必要的严格JSON。我在看你双引号,没有结尾的逗号,也没有评论。
  • 我可以在正在使用的应用程序中编写测试配置,然后轻松将它们移植到配置文件,而无需进行任何转换和JSON解析。
  • 如果确实需要,可以在配置文件中执行非常简单的脚本。(尽管这应该非常非常有限。)

所以,我的问题是:如果我换了脚,我该如何射击自己的脚?

没有技能的最终用户将不会使用配置文件。对配置文件的任何更改当前都提交给Git,并作为连续部署的一部分部署到我们的服务器中。除非有紧急情况或正在开发中,否则无需进行手动配置更改。

(我考虑过YAML,但有关它的一些问题令我感到不适。因此,目前它不在美国的餐桌上。)


39
不熟练不是您的问题。恶意的。
Blrfl

9
“在美国餐桌旁”是什么意思?
Peter Mortensen

24
“所以,目前它不在美国的餐桌上。” ===“因此,按照美国人的说法,现在已经不在桌子上了。”
主教

7
如果您不喜欢JSON,则应尝试yaml。我非常喜欢它的配置。特别是在涉及较大的字符串时,YAML比JSON更具可读性。
Christian Sauer

5
@bishop在英国英语中“离餐桌”意味着不再考虑,因为在讨论时有在下议院中间的议会动议上进行议会动议供参考,因此也“可供讨论”(议会记录1799- books.google.co.uk/…),AFAIK在美国的含义是相同的,但我不知道您的国会中是否有桌子。
Pete Kirkham

Answers:


92

代替配置文件中使用脚本语言乍看起来不错:你有语言的全部功能可用,并且可以简单地eval()或者import它。实际上,有一些陷阱:

  • 它是一种编程语言,需要学习。要编辑配置,您需要充分了解此语言。配置文件通常具有更简单的格式,更容易出错。

  • 这是一种编程语言,这意味着配置可能难以调试。使用普通的配置文件,您可以查看它并查看为每个属性提供了哪些值。使用脚本,您可能需要先执行它才能查看值。

  • 它是一种编程语言,这使得很难在配置和实际程序之间保持清晰的区分。有时您确实想要这种可扩展性,但是到那时,您可能宁愿在寻找一个真正的插件系统。

  • 它是一种编程语言,这意味着配置可以执行编程语言可以执行的任何操作。因此,要么您正在使用沙盒解决方案,而该解决方案否定了该语言的许多灵活性,要么您对配置作者高度信任。

因此,如果您的工具的读者是开发人员,例如Sphinx config或Python项目中的setup.py,则使用脚本进行配置可能就可以了。其他具有可执行配置的程序是Shell(如Bash)和编辑器(如Vim)。

如果配置包含许多条件部分,或者提供回调/插件,则必须使用编程语言进行配置。直接使用脚本而不是eval()-某些配置字段往往更易于调试(请考虑堆栈跟踪和行号!)。

如果您的配置如此重复,以至于您正在编写脚本以自动生成配置,那么直接使用编程语言也可能是一个好主意。但是,也许更好的配置数据模型可以消除对此类显式配置的需求?例如,如果配置文件可以包含稍后扩展的占位符,则可能会有所帮助。有时会看到的另一个功能是具有不同优先级的多个配置文件,它们可以相互覆盖,尽管这会带来一些自身的问题。

在大多数情况下,INI文件,Java属性文件或YAML文档更适合于配置。对于复杂的数据模型,XML也可能适用。正如您已经指出的那样,尽管JSON是一种很好的数据交换格式,但它在某些方面使它不适合用作人类可编辑的配置文件。


25
有几种配置文件格式“偶然是图灵完成的”,最著名的是sendmail.cf。那将表明使用一种实际的脚本语言可能是有益的,因为该语言实际上为图灵完备而设计的但是,图灵完备性和“俄罗斯方块完备性”是两回事,尽管sendmail.cf可以计算任意函数,但它无法/etc/passwd通过网络发送或格式化硬盘(Python或Perl可以做到)。
约尔格W¯¯米塔格

3
@JörgWMittag都灵的完善并不意味着能够通过网络发送信息或访问硬盘。也就是说,都灵完成度与处理无关,而与I / O无关。例如,CSS被认为是Turin完整的,但不会与您的永久存储混乱​​。您在其他地方说过,“ Idris是一种完全的纯功能语言,因此从定义上来说不是图灵完备的”,这种情况并没有遵循,显然它是都灵完备的。我确信您使用Testris-complete意味着该语言是都灵完整的,但无法执行完整的I / O ...这似乎不是您的意思。
Theraot

6
@Theraot:“总计”表示它总是返回。图灵机可以执行无限循环,即它具有不返回的能力。因此,伊德里斯无法做图灵机做的所有事情,这意味着它不是图灵完整的。所有依赖类型的语言都是如此。依赖类型的语言的全部要点是,您可以决定程序的任意属性,而在图灵完备的语言中,您甚至不能决定琐碎的属性,例如“程序是否停止?” 根据定义,全部语言都不图灵完整的,因为图灵机是局部的。
约尔格W¯¯米塔格

10
“图灵完成” 的定义是“可以实施图灵机”。“俄罗斯方块完成”的定义是“可以实现俄罗斯方块”。这个定义的全部要点是,图灵完备性在现实世界中根本不是很有趣。有很多不图灵完备的有用语言,例如HTML,SQL(1999年前),各种DSL等。OTOH,图灵完备仅表示您可以在自然数上计算函数,但这并不意味着打印到屏幕,访问网络,与用户,操作系统和环境进行交互,所有这些都很重要。
约尔格W¯¯米塔格

4
Edwin Brady之所以使用此示例,是因为许多人认为不是图灵完备的语言不能用于通用编程。我自己也曾经想过,因为毕竟,许多有趣的程序本质上是我们不希望停止的无尽循环,例如服务器,操作系统,GUI中的事件循环,游戏循环。许多程序处理无限数据,例如事件流。我曾经以为您不能用全部语言来写,但是自从我学会了就可以了,所以我觉得为这种能力冠以一个好主意。
约尔格W¯¯米塔格

50

amon的答案中的所有内容+1 。我想添加以下内容:

第一次要从用另一种语言编写的代码中导入相同的配置时,您会后悔使用Python代码作为配置语言。例如,如果代码是项目的一部分,并且是用C ++或Ruby编写的,或者需要其他配置来引入配置,则需要将Python解释器链接为一个库,或在Python协同进程中解析配置,这两种方式尴尬,困难或高昂的开销

今天导入此配置的所有代码都可能是用Python编写的,您也许认为明天也是如此,但是您确定吗?

您说您将在配置中使用逻辑(除了静态数据结构以外的任何方式)(如果有的话),这是很好的,但是如果有任何一点,将来您将很难撤消它,所以您可以移回声明性配置文件。

编辑记录:几个人对这个答案发表评论,说一个项目将完全成功地用另一种语言重新编写的可能性是多少。可以公平地说,可能很少看到完整的向后兼容重写。实际上,我想到的是同一项目的零碎片段(需要访问相同的配置)是以不同的语言编写的。例如,在C ++中提供堆栈以提高速度,在Python中提供批处理数据库清理,一些shell脚本作为胶水。因此,请为这种情况花点时间:)


1
@桅杆,很抱歉,但我不关注。文件名(无论文件名是否以.py结尾)都不在此处。我要说明的是,如果它是用Python编写的,那么您需要一个Python解释器来读取它。
Celada

12
@桅杆我认为您在解析错误。我从这个答案(原始的和编辑的)中得出的观点是,选择以编程语言编写配置文件的选择是,这使得使用另一种语言编写代码更加困难。例如,您决定将您的应用程序移植到Anrdoid / iPhone,并将使用另一种语言。您要么必须(a)依靠手机应用程序上的Python解释器(不理想),要么(b)以与语言无关的格式重写配置,然后重写使用该配置的Python代码,或者(c)维护两种配置格式。
乔恩·本特利

4
@JonBentley我想,如果计划进行多语言项目,那么这种担忧将是相关的。我没有从OP中获得那种印象。此外,使用文本文件进行配置仍然需要其他代码(所有语言)才能进行值的实际解析/转换。从技术上讲,如果他们将Python端限制key=value为config的分配,我不明白为什么Java / C ++程序无法将Python文件读取为纯文本文件,并且如果需要将其移至其他内容,则无法将其解析为相同的文本未来。我认为不需要完整的Python解释器。
code_dredd

3
@ray True,但是答案仍然有用,因为问题不仅仅适用于发布问题的人。如果您使用标准化格式(例如INI,JSON,YAML,XML等),则可能会使用现有的解析库,而不是编写自己的解析库。这样可以将额外的工作减少为仅一个适配器类来与解析库进行接口。如果您将自己限制为key = value,那么这将消除大多数OP首先使用Python的原因,并且您最好还是使用公认的格式。
乔恩·本特利

3
几年前,当我用Lua编写的工具使用Lua脚本作为其配置时,我不得不这样做,然后他们希望我们用C#编写一个新工具,并特别要求我们使用Lua脚本。他们总共有2行实际上是可编程的,而不是简单的x = y,但是由于它们,我仍然不得不学习.net的开源Lua解释器。这不是纯粹的理论论证。
凯文·费

21

其他答案已经非常好,我将在一些项目中介绍我的实际用法。

优点

它们大部分已经被阐明:

  • 如果您使用的是Python程序,则解析起来很容易(eval);它甚至可以自动用于更复杂的数据类型(在我们的程序中,我们具有几何点和变换,可以通过repr/ 很好地进行转储/加载eval);
  • 仅用几行代码创建一个“假配置”是微不足道的;
  • 您拥有更好的结构,并且IMO的语法也比JSON更好(jeez甚至只是具有注释,而不必在字典键周围加上双引号是可读性的一大胜利)。

缺点

  • 恶意用户可以执行您的主程序可以执行的任何操作;我认为问题不大,因为通常,如果用户可以修改配置文件,则他/她已经可以执行应用程序可以执行的任何操作;
  • 如果您不再使用Python程序,现在就遇到了问题。尽管我们的几个配置文件保留了其原始应用程序的私有性,但其中一个特别用于存储由多个不同程序使用的信息,其中大多数程序当前都在C ++中,而这些程序现在都有一个共同使用的解析器,用于定义不明确的小程序。 Python的子集repr。这显然是一件坏事。
  • 即使您的程序仍使用Python,也可以更改Python版本。假设您的应用是从Python 2开始的;经过大量测试后,您设法将其迁移到Python 3-不幸的是,您并未真正测试所有代码-您所有的配置文件都位于客户计算机上,是为Python 2编写的,而您没有真的没有控制权。除非您愿意捆绑/调用Python 2解释器,否则您甚至无法提供“兼容模式”来读取旧的配置文件(通常是针对文件格式进行的操作)!
  • 即使您使用的是Python,从代码中修改配置文件也是一个真正的问题,因为……好吧,修改代码根本不是一件容易的事,尤其是语法丰富且不在LISP或类似语言中的代码。我们的一个程序有一个配置文件,它是Python,最初是手工编写的,但后来证明通过软件进行操作将很有用(一个特殊的设置是一系列的事情,使用GUI 可以更轻松地对其进行重新排序)。这是一个大问题,因为:

    • 即使只是执行parse→AST→rewrite往返也不是一件容易的事(您会注意到,提议的解决方案中有一半后来被标记为“过时,不使用,在所有情况下均不起作用”);
    • 即使他们工作了,AST也太低级了;您通常对处理文件中执行的计算结果感兴趣,而不是对文件执行操作感兴趣;
    • 这使我们想到了一个简单的事实,即您不能只编辑您感兴趣的值,因为它们可能是由您无法通过代码理解/操纵的复杂计算生成的。

    将此与JSON,INI或(上帝禁止!)XML进行比较,在这种情况下,始终可以编辑和写回内存中的表示形式而不会丢失数据(XML,其中大多数DOM解析器可以在文本节点和注释节点中保留空格),或者至少会丢失某些格式(JSON,其中格式本身所允许的内容不超过您正在读取的原始数据)。


因此,像往常一样,没有明确的解决方案。我目前在该问题上的政策是:

  • 如果配置文件是:

    • 当然,对于Python应用程序来说,它是私有的-例如,没有其他人会尝试读取它;
    • 手写
    • 来自受信任的来源;
    • 使用目标应用程序数据类型确实是一种奖励。

    Python文件可能是一个有效的主意;

  • 如果相反:

    • 可能有其他一些应用程序从中读取;
    • 该文件可能会被某个应用程序(甚至是我的应用程序本身)编辑;
    • 由不受信任的来源提供。

    “仅数据”格式可能是一个更好的主意。

请注意,不需要做出单一选择-我最近编写了使用这两种方法的应用程序。我有一个几乎从未修改过的文件,该文件具有首次设置,手写设置以及可以从UI编辑的用于配置的JSON文件,这些设置具有获得不错的Python奖励的优势。


1
关于生成或重写配置的好点!但是除XML之外,几乎没有其他格式可以在输出中保留注释,我认为这对于配置非常重要。其他格式有时会引入一个note:在配置中忽略的字段。
阿蒙(Amon)

2
“如果用户可以修改配置文件,他/她已经可以执行应用程序可以执行的任何操作”-这不是真的。如何测试不认识的人上传到pastebin上的闪亮配置文件?
德米特里·格里戈里耶夫

2
@DmitryGrigoryev:如果您要瞄准那个目标,您最好告诉受害者复制粘贴一些东西curl ... | bash,这更不用麻烦了。:-P
Matteo Italia

@DmitryGrigoryev:这种类型的东西可能会让某人在工作的第一天就完全破坏生产系统。如果“ eval”是您的解析器,则意味着在读取前没有机会检查它是否存在问题。(shell脚本在生产中如此糟糕的相同原因)。在这方面,INI,YAML或JSON是安全的。

1
@DmitryGrigoryev:我的意思是,如果您的受害者类型很愚蠢,无法盲目地复制-粘贴配置文件,则您可能会诱使他/她使用较少倾斜的方法在他们的计算机上执行任何操作(“将其粘贴到控制台以解决您的问题!”)。此外,即使使用不可执行的配置文件,也有很大的潜在危害-即使只是恶意指向关键文件的日志记录(如果应用程序以足够的特权运行),也可能给系统造成严重破坏。这就是为什么我认为在实践中安全性没有太大区别的原因。
Matteo Italia

8

主要问题是:您是否希望配置文件使用某种图灵完整的语言(例如Python)?如果您确实希望这样做,则还可以考虑嵌入其他(如Turnle完整的)脚本语言,例如GuileLua(因为与Python相比,使用或嵌入它们可能被认为“更简单”;请阅读“ 扩展和嵌入Python)。我不会讨论进一步的(因为-EG其他答案由阿蒙 -讨论了深),但请注意,在你的应用程序中嵌入的脚本语言是一个重大的架构选择,你应该考虑的非常; 我真的不建议以后再做出选择!

可以通过“脚本”进行配置的程序的一个著名示例是GNU emacs编辑器(或专有领域中的AutoCAD)。因此请注意,如果您接受脚本编写,则某些用户最终会广泛使用该工具(可能会滥用),并制作成千上万行的脚本;因此,选择足够好的脚本语言非常重要。

但是(至少在POSIX系统上),您可能认为方便在初始化时动态计算配置“文件”(当然,将合理配置的负担留给了系统管理员或用户;实际上它是一种配置)来自某个文件或命令的文本)。为此,您可以简单地采用一种约定(并将其记录下来),即以a !或a 开头的配置文件路径|实际上是要作为管道读取的shell 命令。这使您的用户可以选择使用他最熟悉的“预处理器”或“脚本语言”。

(如果您接受动态计算的配置,则需要让用户信任安全问题)

因此,在您的初始化代码中,您main将(例如)接受一些--config 参数 confarg并从中获取一些参数FILE*configf;。如果该参数以!(即(confarg[0]=='!')....)开头,则可以使用configf = popen(confarg+1, "r");并关闭该管道pclose(configf);。否则,您将使用configf=fopen(confarg, "r");并关闭该文件fclose(configf);(不要忘记进行错误检查)。参见pipe(7)popen(3)fopen(3)。对于以Python编码的应用程序,请阅读有关os.popen等的信息。

(文件还为希望通过名为的配置文件中的怪异用户!foo.config通过./!foo.config绕过popen上述特技)

顺便说一句,这种技巧只是一种方便(避免要求高级用户例如编写一些shell脚本来生成配置文件)。如果用户要报告任何错误,则应将生成的配置文件发送给您...

注意,您还可以设计具有在初始化时使用和加载插件的功能的应用程序,例如使用dlopen(3)(并且您需要让用户信任该插件)。同样,这是一个非常重要的体系结构决策(您需要定义并提供有关这些插件和应用程序的相当稳定的API和约定)。

对于以脚本语言(如Python)编码的应用程序,您还可以接受一些用于evalexec或类似基元的程序参数。同样,(高级)用户会担心安全问题

关于您的配置文件中的文本格式(无论是产生或没有),我相信你大多需要记录它(和一些特定格式的选择是那么重要,但是我建议让您的用户能够把里面有一些跳过的注释)。你可以使用JSON(最好带些JSON解析器接受和跳过与通常的意见//,直至停产或/*... */...),或YAML或XML,或INI或你自己的事情。解析配置文件相当容易(并且您会发现许多与该任务相关的库)。


+1表示编程语言的图灵完备性。一些有趣的工作表明,限制输入格式的计算能力是确保输入处理层安全的关键。使用图灵完备的编程语言会朝相反的方向发展。
Matheus Moreira

2

除了amon的答案外,您是否考虑过替代方案?JSON可能比您需要的更多,但是由于上述原因,Python文件将来可能会给您带来问题。

但是,Python已经具有用于非常简单的配置语言的配置解析器,可以满足您的所有需求。该ConfigParser模块实现了一个简单的配置语言。


1
使用“类似于... Microsoft Windows INI文件”似乎不是一个好主意,原因是它不是一种特别灵活的格式,并且因为“相似”表示未记录的不兼容性。
Pete Kirkham

1
@PeteKirkham好吧,它很简单,已记录在案,它是Python标准库的一部分。这可能是OP需求的完美解决方案,因为他正在寻找Python直接支持且比JSON更简单的东西。只要他没有进一步说明他的需求是什么,我认为这个答案可能会对他有所帮助。
CodeMonkey

1
我实际上是在建议这个-查看Python库支持哪些类型的配置文件,然后选择其中一种。而且,Powershell具有数据部分的概念-允许使用有限的Powershell语言构造-防止恶意代码。如果Python具有支持配置的Python有限子集的库,则至少可以减轻与OP中的想法相反的缺点之一。
Χpẘ

1
@PeteKirkham相反,它更有可能是一个问题。Windows往往会产生大量未记录的废话,这些废话会在您身上爆炸。Python趋向于文档齐全且简单明了。就是说,如果您只需要简单的键/值对(可能带有部分),那将是一个不错的选择。我怀疑这涵盖了90%的用例。如果.NET的配置文件是ini而不是具有实际上伪装成config的模式的原始XML,那么我们都会好多了。
jpmc26

1
@PeteKirkham不是。INI首先是简单用例的最佳选择,很可能您可以避免任何不兼容性。如果您不使用两种不同的语言来使用文件,它们也没有关系,即使您使用的是两种语言,也可能会找到任何一种语言的开放式实现(允许您不存在兼容性问题,或者至少了解确切的含义)。他们是)。我同意,如果用例实际上足够复杂以至于您开始使用它们,或者如果找不到可以信任的现有实现,则应该使用另一种格式,但这并不常见。
jpmc26 2015年

1

我使用某些知名软件工作了很长时间,该软件的配置文件用TCL编写,因此这个想法并不新鲜。这非常有效,因为不了解该语言的用户仍然可以使用单个set name value语句来编写/编辑简单的配置文件,而更高级的用户和开发人员可以以此来获取复杂的技巧。

我认为“配置文件可能难以调试”不是一个有效的问题。只要您的应用程序不强迫用户编写脚本,您的用户就可以始终在其配置文件中使用简单的分配,与JSON或XML相比,这几乎很难解决。

重写配置是一个问题,尽管它看起来还不算糟糕。更新任意代码是不可能的,但是从文件中加载配置,更改并保存回去是可以的。基本上,如果您在非只读的配置文件中执行一些脚本编写,则一旦保存该文件,您将得到等价的set name value语句列表。一个很好的暗示,即将发生这种情况的是文件开头的“请勿编辑”注释。

要考虑的一件事是,您的配置文件将无法通过基于regex的简单工具可靠地读取sed,但据我了解,当前的JSON文件已经不是这种情况了,因此不会有太多损失。

只需确保在执行配置文件时使用适当的沙箱技术即可。


1
“软件”是一个不可数名词,因此应为“ 某些知名软件”。
jpmc26

1

除了这里其他好的答案的所有有效点(哇,他们甚至提到了图灵完备的概念),实际上还有很多可靠的实际原因,即使在使用Python时,也不使用Python文件作为配置-唯一的项目。

  1. 从技术上讲,Python源文件中的设置是可执行源代码的一部分,而不是只读数据文件。如果走这条路,通常会这样做import config,因为这种“便利”可能是人们首先开始使用Python文件作为配置的主要原因之一。现在,您倾向于将该config.py提交到您的存储库中,否则最终用户在首次尝试运行程序时会遇到令人困惑的ImportError。

  2. 假设您实际上将config.py提交到您的仓库中,那么您的团队成员可能在不同的环境中具有不同的设置。想像一下,有一天某位成员不小心将其本地配置文件提交到了仓库中。

  3. 最后但并非最不重要的一点是,您的项目可能在配置文件中具有密码。(这本身是值得商practice的做法,但是还是会发生这种情况。)而且,如果您的配置文件存在于回购中,则有将证书提交到公共回购中的风险。

现在,使用仅数据配置文件(例如通用JSON格式)可以避免上述所有3个问题,因为您可以合理地要求用户提供自己的config.json并将其输入到程序中。

PS:的确,JSON有很多限制。OP提到的2个局限性可以通过一些创造力来解决。

  • 如何将注释放入JSON文件(正确)
  • 而且我通常会使用一个占位符来绕开尾部的逗号规则。像这样:

    {
        "foo": 123,
        "bar": 456,
        "_placeholder_": "all other lines in this file can now contain trailing comma"
    }
    
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.