如何以确定的顺序打印 jinja dict?

How can I print a jinja dict in a deterministic order?

我有一个配置文件,其中的特定设置需要很长很丑的 json 风格的字典。我想在 YAML 中定义这个字典并用 Jinja 打印出来。目前看起来像这样:

{%- load_yaml as some_conf_val %}
val1: something
val2: completely
val3: different
{%- endload %}

option = {{ some_conf_val }}

# Results in:
option = {'val2': 'completely', 'val1': 'something', 'val3': 'different'}

巧合的是,这正是正在配置的程序所期望的格式,而且 yaml 块比内联版本更容易阅读和修改。然而,键既不按字母顺序也不按它们定义的顺序出现这一事实让我怀疑它们的输出顺序是不确定的。在重新运行状态几次他们总是以相同的顺序出现,但这并不能证明什么。

这是一个问题,因为如果更改了输出字符串,它会被视为对文件的更改并触发服务重启,即使没有任何功能更改。我不在乎键的具体顺序,但我确实需要每次都相同的顺序。

我怎样才能做到这一点?还是它已经是确定性的,只是看起来不像?

(如果我理解正确的话,jinja 字典实际上是 python 引擎盖下的字典,并且 python 字典是无序的,所以这可能是不可能的,而不包括比我的行更混乱的代码尝试不必写。但我希望不会。)

你是对的,Python的标准字典是无序的。 collections 模块中有一个订购版本:collections.OrderedDict。我们可以将它与自定义 Jinja2 过滤器一起用于此目的。

自定义过滤器先将输入转换为OrderedDict,再转换为JSON,最后替换双引号:

from json import dumps
from collections import OrderedDict

def conffilter(value):
    return dumps(OrderedDict(value)).replace('"', '\'')

然后我们要add this new filter到Jinja2:

 env.filters['conffilter'] = conffilter

其中 env 是您应用中的 Jinja2 Environment 对象(例如,在 Flask 中是 app.jinja_env)。

现在我们可以将新过滤器与 Jinja2 的 dictsort 过滤器一起使用。

# Define a list for testing:
{%- set some_conf_val = {'val2': 'completely', 'val1': 'something', 'val3': 'different'} -%}

# Unordered dict output
option = {{ some_conf_val }}
# Result:
option = {'val1': 'something', 'val3': 'different', 'val2': 'completely'}

# Ordered list of tuples
option = {{ some_conf_val|dictsort }}
# Result:
option = [('val1', 'something'), ('val2', 'completely'), ('val3', 'different')] 

# Ordered dict-like output:
option = {{ some_conf_val|dictsort|conffilter }}
# Result:
option = {'val1': 'something', 'val2': 'completely', 'val3': 'different'} 

事实证明,saltstack 为 json 输出提供了一个过滤器,可以对其键进行排序,并且可以进行按摩以获得我想要的。我最终使用的解决方案如下所示:

option = {{ some_conf_val|json|replace('"', "'") }}

# Results in:
option = {'val1': 'something', 'val2': 'completely', 'val3': 'different'}

替换操作是因为该选项最终被提供给关心它正在查看哪种引号的东西。它可能不适用于其他情况。

据我所知,排序行为没有记录,但您可以在源代码中找到它 here