PyYaml 结合两个 yaml 文件

Posted

技术标签:

【中文标题】PyYaml 结合两个 yaml 文件【英文标题】:PyYaml combining two yaml files 【发布时间】:2016-06-01 06:24:11 【问题描述】:

我正在尝试建立一个系统,其中有几个(可能更多)用于配置的 yaml 文件。我希望能够在另一个文件中引用一个文件中的内容。

我知道 YAML 不允许这样做。

我认为我的计划是将两个 YAML 文件合并,然后将其视为一个文件。我很确定我可以将这两个文件放在一起,创建一个临时文件,然后将其读取为 YAML,或者将文件读取为文本,将它们连接起来,然后解析字符串。

但是,我觉得应该有更好的方法来做到这一点。有没有?

【问题讨论】:

我认为这已经得到了回答:***.com/questions/47424865/… @Tom,我的问题来自 5.5 年前。您指向的是 4 年前...只是让您知道... 【参考方案1】:

在 YAML 中引用的唯一方法是使用 &(锚点)和 *(别名)。为了使它们起作用,它们必须在同一个 YAML 文档中。以下将不起作用(这是基于merge key 功能,但普通对象引用具有相同的限制):

import ruamel.yaml

yaml_str = """\
a: &BASE  x: 1, y: 2
---
b:
  << : *BASE
  z: 3
"""

for data in ruamel.yaml.load_all(yaml_str):
    print(data)

抛出未找到“BASE”的作曲家错误。删除--- 文档分隔符,一切都很好。

所以原则上连接两个文档是可行的。如果不将其与包含它的文档连接起来,就无法单独加载具有别名的文档。

另外需要注意的是,所有文档都必须在顶层具有映射或序列。如果会组合一个序列:

- &BASE a
- b

带有映射:

c: 1
d: *BASE

结果将无法加载。


如前所述,如果所有文件的***类型相同,则无法加载 YAML 文件并将它们组合到内存中。 IE。鉴于合并密钥文档中的示例拆分为1.yaml

- &CENTER  x: 1, y: 2 
- &LEFT  x: 0, y: 2 
- &BIG  r: 10 
- &SMALL  r: 1 

2.yaml:

# Explicit keys
-
  x: 1
  y: 2
  r: 10
  label: center/big

3.yaml:

# Merge one map
-
  << : *CENTER
  r: 10
  label: center/big

4.yaml:

# Merge multiple maps
-
  << : [ *CENTER, *BIG ]
  label: center/big    

5.yaml:

# Override
-
  << : [ *BIG, *LEFT, *SMALL ]
  x: 1
  label: center/big

不能在单个 YAML 文件上使用 load() 并将它们组合起来:

import ruamel.yaml
import glob

data = []
for file_name in sorted(glob.glob('*.yaml')):
    data.append(ruamel.yaml.load(open(file_name)))
print(ruamel.yaml.dump(data, allow_unicode=True))

(如果2.yaml 等没有别名,上述方法将起作用)

如果您不想在程序之外连接文件,您可以 使用这个类:

class CombinedOpenForReading(object):
    def __init__(self, file_names):
        self._to_do = file_names[:]
        self._fp = None

    def __enter__(self):
        return self

    def __exit__(self, exception_type, exception_value, exception_traceback):
        if self._fp:
            self._fp.close()

    def read(self, size=None):
        res = ''
        while True:
            if self._fp is None:
                if not self._to_do:
                    return res
                else:
                    self._fp = open(self._to_do.pop(0))
            if size is None:
                data = self._fp.read()
            else:
                data = self._fp.read(size)
            if size is None or not data:
                self._fp.close()
                self._fp = None
            res += data
            if size is None:
                continue
            size -= len(data)
            if size == 0:
                break
        return res

要做的事情:

import ruamel.yaml
import glob

with CombinedOpenForReading(sorted(glob.glob('*.yaml'))) as fp:
    data = ruamel.yaml.round_trip_load(fp)
assert data[6]['r'] == 10
print(ruamel.yaml.dump(data, Dumper=ruamel.yaml.RoundTripDumper))

得到:

- &CENTER x: 1, y: 2
- &LEFT x: 0, y: 2
- &BIG r: 10
- &SMALL r: 1
# Explicit keys
- x: 1
  y: 2
  r: 10
  label: center/big
# Merge one map
- <<: *CENTER
  r: 10
  label: center/big
# Merge multiple maps
- <<: [*CENTER, *BIG]
  label: center/big
# Override
- <<: [*BIG, *LEFT, *SMALL]
  x: 1
  label: center/big

(您必须按正确的顺序提交文件,从而进行排序。并确保文件末尾有换行符,否则可能会出现意外错误。)

【讨论】:

您的警告是不是意味着 &BASE 和 *BASE 必须属于同一类事物(序列与映射)?我没有意识到这是一个问题,但它不应该是一个问题。我不打算在一个文件中使用多个文档。我打算在一个文档中使用多个文件。 啊,你的意思是为了使文件可以合并,它们必须具有相同的最顶层类型。是的,这应该不是问题。 @BrianPostow 是的,最上面的类型必须相同【参考方案2】:

我认为这比@Anthon 的简单。它可能并不完整,但我认为这就是我所需要的......

def merge(fList):
    ''' 
    Takes a list of yaml files and loads them as a single yaml document.
    Restrictions:
    1) None of the files may have a yaml document marker (---)
    2) All of the files must have the same top-level type (dictionary or list)
    3) If any pointers cross between files, then the file in which they are defined (&) must be 
    earlier in the list than any uses (*).
    '''

    if not fList:
        #if flist is the empty list, return an empty list. This is arbitrary, if it turns out that
        #an empty dictionary is better, we can do something about that.
        return []

    sList = []
    for f in fList:
        with open(f, 'r') as stream:
            sList.append(stream.read())
    fString = ''
    for s in sList:
        fString = fString + '\n'+ s

    y = yaml.load(fString)

    return y

欢迎参加。

【讨论】:

合并三个文件,完全替换某些部分而不是合并它们 怎么样?如果它们是字典,并且键不相交,那当然可以,但这可能是 YAML 错误... 我真的不知道。最终使用了 merge-yaml npm cli 工具。而且那里也有问题,所以我不得不将一个文件移动到第二个位置:D 我想知道这怎么可能。甚至还有可用的 yamlreader 来检查 yaml 文件语法错误并对其进行格式化,但仍然没有帮助

以上是关于PyYaml 结合两个 yaml 文件的主要内容,如果未能解决你的问题,请参考以下文章

为啥致命错误:安装 PyYAML 时找不到“yaml.h”文件?

PyYaml“包含文件”和 yaml 别名(锚点/引用)

如果有“!”,如何使用 PyYAML 解析 YAML在 YAML 中

Python + PyYAML 读取yaml配置文件数据

在 PyYAML 中保存/转储带有注释的 YAML 文件

为啥 PyYAML 仅仅在解析 YAML 文件上花费了这么多时间?