我一直使用JSON文件配置应用程序。我从编写大量Java时就开始使用它们,现在我主要从事服务器端和数据科学Python开发,并且不确定JSON是否是正确的选择。
我'我们已经看到Celery使用实际的Python文件进行配置。最初我对此持怀疑态度。但是,使用简单的Python数据结构进行配置的想法开始对我产生影响。一些优点:

数据结构将与我通常编码的数据结构相同。因此,我不需要改变心意。配置和代码之间的连接。 Ctrl + B使得在配置和代码之间轻松切换成为可能。
我不需要使用IMO不必要的严格JSON。我正在看的是双引号,没有结尾的逗号,也没有注释。
我可以在正在处理的应用程序中编写测试配置,然后轻松地将它们移植到配置文件中,而无需进行任何转换和JSON解析。
如果确实需要,可以在配置文件中执行非常简单的脚本。 (尽管这应该非常非常有限。)

所以,我的问题是:如果切换,我该如何用脚射击自己?
没有熟练的最终用户将不会使用配置文件。对配置文件的任何更改当前都提交给Git,并作为连续部署的一部分部署到我们的服务器中。除非有紧急情况或正在开发中,否则无需进行手动配置更改。
(我已经考虑过YAML,但有关它的某些问题令我感到不安。因此,现在它不在桌面上。)

评论

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

@Blrfl是的。这是我主要关心的问题之一。当前,如果您可以更改配置文件,则还可以更改代码。我看到的问题是,将来我们在团队成员之间传递配置文件时,会有更多的职责分离,其中一些被允许进行编码更新,而其他则没有。这还很遥远,但将来可能会成为现实。基于Python的配置文件将成为安全问题。

使用JSON作为配置。许多语言都了解JSON(包括Python,所以有人可能会说它使用的是Python的子集),它简短易懂。而且您也不会尝试在配置文件中编写更复杂的“代码”。

根本问题是python是Turing-complete,而JSON不是。因此,没有程序可以可靠地预测python脚本的行为。如果您的IDE似乎能理解python,那么就不会像软件可以理解JSON文件那样理解它。 Birfl说:不熟练不是您的问题。恶意的。为了对此进行放大,可以使用简单的自动化方法来防御恶意JSON,但是即使在原则上也没有办法对恶意python进行自动化保护。听起来您真正想要的是一种更具表现力的图灵不完整语言。

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

#1 楼

乍一看,使用脚本语言代替配置文件看起来很不错:您拥有该语言的全部功能,可以简单地对其进行eval()import的处理。实际上,有一些陷阱:


它是一种编程语言,需要学习。要编辑配置,您需要充分了解此语言。配置文件通常具有更简单的格式,更容易出错。
这是一种编程语言,这意味着配置可能很难调试。使用普通的配置文件,您可以查看它并查看为每个属性提供了哪些值。使用脚本,您可能需要先执行它才能查看值。
这是一种编程语言,这使得很难在配置和实际程序之间保持清晰的分隔。有时您确实想要这种可扩展性,但是到那时,您可能正在寻找一个真正的插件系统。
它是一种编程语言,这意味着config可以执行该编程语言可以完成的任何工作。因此,要么您使用的是沙箱解决方案,否定了该语言的大部分灵活性,要么是您高度信任config作者。

因此,如果听众喜欢使用脚本进行配置您的工具是开发人员,例如Sphinx配置或Python项目中的setup.py。其他具有可执行配置的程序是Bash这样的shell,以及Vim这样的编辑器。

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

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

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

评论


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

–Jörg W Mittag
17年6月18日14:25



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

– Theraot
17 Jun 19'在0:12



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

–Jörg W Mittag
17年6月19日在0:17



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

–Jörg W Mittag
17 Jun 19'在0:21



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

–Jörg W Mittag
17年6月19日,0:24



#2 楼

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

您将第一次使用Python代码作为配置语言时感到遗憾,因为您希望从用另一种语言编写的代码中导入相同的配置。例如,如果代码是项目的一部分,并且是用C ++或Ruby编写的,或者需要其他配置来提取配置,则需要将Python解释器链接为一个库,或在Python协同进程中解析配置,笨拙,困难或高开销。

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

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

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

评论


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

–塞拉达
17年6月18日在19:01

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

–乔恩·本特利
17年6月18日在19:37

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

– code_dredd
17年6月18日在19:54



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

–乔恩·本特利
17年6月18日在23:33



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

–凯文费
17年6月19日在15:55

#3 楼

其他答案已经非常好,我将在一些项目中介绍我在现实世界中的使用经验。 br />

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

缺点


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

即使您使用的是Python,也无法通过代码修改配置文件,因为……好吧,修改代码根本不是一件容易的事,尤其是语法丰富且不在LISP或类似语言中的代码。我们的一个程序有一个配置文件,它是Python,最初是手工编写的,但后来证明通过软件进行操作将很有用(一个特定的设置是一系列的事情,使用GUI可以更轻松地对其进行重新排序)。这是一个大问题,因为:


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

与JSON,INI或(禁止上帝!)XML进行比较,可以随时编辑和编写内存中表示形式返回或者不丢失数据(XML,大多数DOM解析器可以在文本节点和注释节点中保留空白),或者至少丢失某些格式(JSON,其中格式本身所允许的内容比读取的原始数据多得多) )。



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



如果配置文件是:


对于Python应用程序肯定是私有的,它-就像其他人一样,永远不会尝试读取它;
手写的;
来自受信任的来源;
使用目标应用程序数据类型确实是一种溢价;

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


如果相反,则:


可能有其他应用程序从中读取;
有可能该文件可能由应用程序编辑,甚至可能是我的应用程序本身;
由不受信任的来源提供。 br />
“仅数据”格式可能是一个更好的主意。


请注意,不必做出单一选择-我最近编写了一个应用程序方法。我有一个几乎没有修改过的文件,它具有首次设置,手写设置,这些设置具有获得不错的Python奖金的优势,还有一个用于从UI编辑的用于配置的JSON文件。

评论


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

–阿蒙
17年6月19日在8:33

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

–德米特里·格里戈里耶夫(Dmitry Grigoryev)
17年6月19日在13:22

@DmitryGrigoryev:如果您的目标是目标,您最好告诉受害者复制粘贴一些卷发... | bash,这更省事了。 :-P

–意大利Matteo
17年6月19日在13:47

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

–乔
17年6月19日在20:11

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

–意大利Matteo
17年6月20日在9:07



#4 楼

主要问题是:您是否希望配置文件使用某种图灵完整的语言(例如Python)?如果您确实希望这样做,则还可以考虑嵌入其他(如Turning完整的)脚本语言,例如Guile或Lua(因为使用或嵌入比Python更“简单”;请阅读“扩展和嵌入Python)。我将不做进一步讨论(因为其他答案,例如Amon进行了深入讨论),但是请注意,在应用程序中嵌入脚本语言是主要的体系结构选择,因此应尽早考虑。我真的不建议以后再做选择!

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

但是(至少在POSIX系统上),您可能认为方便在初始化时动态计算配置“文件”(当然) ,将合理配置的负担留给了系统管理员或用户;实际上,它是来自某些文件或命令的配置文本)。为此,您可以简单地采用(并记录下来)约定以配置文件路径开头,例如一个!或一个|实际上是一个您将被视为管道的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)编码的应用程序,也可以接受一些用于eval或exec或类似基元的程序参数。同样,(高级)用户也要关心安全问题。

关于配置文件的文本格式(无论是否生成),我认为您最需要将其文档化(并且某些特定格式的选择并不那么重要;但是我建议您让用户能够输入里面有一些跳过的注释)。您可以使用JSON(最好是使用某些JSON解析器通过通常的//接受和跳过注释,直到eol或/* ... */ ...),YAML,XML,INI或您自己的东西。解析配置文件相当容易(并且您会发现许多与此任务相关的库)。

评论


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

–马修斯·莫雷拉(Matheus Moreira)
17年6月23日在4:17

#5 楼

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

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

评论


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

– Pete Kirkham
17年6月19日在13:03

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

– CodeMonkey
17年6月19日在13:08

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

–Χpẘ
17年6月19日在22:42

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

– jpmc26
17年6月20日在7:29



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

– jpmc26
17年6月20日在15:06



#6 楼

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

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

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

要考虑的一件事是,基于简单正则表达式的配置文件无法可靠地读取您的配置文件工具,例如sed,但据我所知,当前的JSON文件已经不是这种情况了,因此不会有太多损失。配置文件。

评论


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

– jpmc26
17年6月20日在7:34



#7 楼

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


Python源文件中的设置从技术上讲是可执行源代码的一部分,而不是只读数据文件。如果走这条路,通常会执行import config,因为这种“便利”可能是人们首先使用Python文件作为配置的主要原因之一。现在,您倾向于将该config.py提交到您的存储库中,否则最终用户在首次运行程序时会遇到一个令人困惑的ImportError。
假设您实际上将该config.py提交到了存储库中,现在您的团队成员可能在不同的环境中具有不同的设置。想像一下,有一天某位成员不小心将其本地配置文件提交到了仓库中。
最后但并非最不重要的是,您的项目可能在配置文件中包含密码。 (这本身是值得商de的做法,但是还是会发生这种情况。)而且,如果您的配置文件存在于回购中,则您可能会将凭证提交到公共回购中。

现在,仅使用数据配置文件(例如通用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"
}