【问题标题】:How to update YAML file with value coming from a matching key in another dictionary?如何使用来自另一个字典中匹配键的值更新 YAML 文件?
【发布时间】:2018-09-27 23:39:41
【问题描述】:

我有一个名为 input.yaml 的 YAML 文件:

---
'001':
  name: Ben
  email: ben@test.com
'002':
  name: Lisa
  email: lisa@test.com
'003':
  name: Alex
  email: alex@test.com
.
.
.

我有一本字典:

my_dict = {'001': '000-111-2222', '002': '000-111-2223', '003': '000-111-2224', ...}

我想要一个名为output.yaml 的更新文件,如下所示:

---
'001':
  name: Ben
  email: ben@test.com
  phone: 000-111-2222
'002':
  name: Lisa
  email: lisa@test.com
  phone: 000-111-2223
'003':
  name: Alex
  email: alex@test.com
  phone: 000-111-2224
.
.
.

注意输出文件如何在“电话”字段中添加来自匹配键的字典值的值。

我如何获得这样的文件? ...我已经尝试了各种方式。

【问题讨论】:

    标签: python dictionary yaml pyyaml ruamel.yaml


    【解决方案1】:

    除了读取和写入文件之外,也许这会为您指明正确的方向:

    import yaml
    
    
    document = """
    ---
    '001':
      name: Ben
      email: ben@test.com
    '002':
      name: Lisa
      email: lisa@test.com
    '003':
      name: Alex
      email: alex@test.com
    """
    
    phones = {'001': '000-111-2222', '002': '000-111-2223', '003': '000-111-2224'}
    
    doc = yaml.safe_load(document)
    
    for k, v in phones.items():
        # Might want to check that 'doc[k]' exists
        doc[k]['phone'] = v
    
    print(yaml.safe_dump(doc, default_flow_style=False, explicit_start=True))
    

    输出:

    '001':
      email: ben@test.com
      name: Ben
      phone: 000-111-2222
    '002':
      email: lisa@test.com
      name: Lisa
      phone: 000-111-2223
    '003':
      email: alex@test.com
      name: Alex
      phone: 000-111-2224
    

    【讨论】:

    • 这似乎在添加电话字段方面起作用,但是,它改变了文件的格式......我怎样才能保留格式和状态?并且只需在电子邮件字段后添加电话字段?
    • @Becks 您可以使用OrderedDict 来实现此目的,请参阅this answer
    • 没有必要使用不安全的load(),您应该始终使用safe_load()。 PyYAML 是基于流的,如果您不提供流,则可以回退到缓冲区并提供字符串。因此,您的print(yaml.dump(doc)) 速度缓慢且效率低下,应该写成yaml.safe_dump(doc, sys.stdout)(它可能会导致您的内存不足)。您的输出不包含 OP 请求的文档开始标记,您可以为此使用参数 explicit_start=True
    • @Anthon,谢谢我应用了编辑。但是,我将其保留为 print,因为它实际上是一个调试语句,而不是实际结果,因为它“应该”被写入文件。
    • @AaronN.Brock 我能理解你对print() 的看法,它比yaml.safe_dump(doc, sys.stdout) 更明显是一个调试语句。当您看到代码使用print(yaml.safe_dump(doc), file=some_file_pointer) 转储大型 YAML 文件(数百 Mb)时,也许您会有不同的看法,就像我在评论中所说的那样 ;-)
    【解决方案2】:

    如果您担心文件的格式保持不变(并且如果存在应该保留的 cmets),您可以这样做:

    import ruamel.yaml
    
    yaml = ruamel.yaml.YAML()
    yaml.preserve_quotes = True
    yaml.explicit_start = True
    
    with open('input.yaml') as fp:
        data = yaml.load(fp)
    
    my_dict = {
        '001': '000-111-2222',
        '002': '000-111-2223',
        '003': '000-111-2224',
    }
    
    for k in my_dict:
        data.setdefault(k, {})['phone'] = my_dict[k]
    
    with open('output.yaml', 'w') as fp:
        yaml.dump(data, fp)
    

    之后output.yaml 包含:

    ---
    '001':
      name: Ben
      email: ben@test.com
      phone: 000-111-2222
    '002':
      name: Lisa
      email: lisa@test.com
      phone: 000-111-2223
    '003':
      name: Alex
      email: alex@test.com
      phone: 000-111-2224
    

    注意事项:

    1. yaml.preserve_quotes = True 并不是真正需要的,因为对于需要引号的标量(您的字符串以零开头),单引号是默认值,并且您的输入中也没有多余的引号。

    2. 我使用data.setdefault(k, {})['phone'],而不是像@Aaron 在他的来源中建议的那样检查data[k] 是否存在。如果密钥 k 不在 data 中,它将创建一个(空)dict。

    3. 如果您只想更新 匹配 键,请在 for 循环中使用以下内容:

      try:
          data[k]['phone'] = my_dict[k]
      except KeyError:
          pass
      
    4. 您需要yaml.explicit_start = True 才能在文档开始时获取---ruamel.yaml 不会自动保留它。如果您还需要文档结束标记 (...),请使用:yaml.explicit_end = True

    5. 如果您希望电话号码出现在nameemail 之间,请使用:

      data.setdefault(k, {}).insert(1, 'phone', my_dict[k])
      

      给出:

      ---
      '001':
        name: Ben
        phone: 000-111-2222
        email: ben@test.com
      '002':
        name: Lisa
        phone: 000-111-2223
        email: lisa@test.com
      '003':
        name: Alex
        phone: 000-111-2224
        email: alex@test.com
      

      (即0表示在第一个键之前插入,1表示在第二个键之前插入等)

    【讨论】:

    • 只是一个建议。如果用户拥有 2 个以上的电话号码,此代码将不起作用。也许在您的代码中也考虑到这一点:)
    • @Dirk 为什么这不起作用?如果 my_dict 中的值是(电话号码)字符串列表而不是单个字符串,则代码仍然有效 AFAICT
    • 尝试运行 my_dict = { '001': '000-111-2222', '001': '000-314-2222', '002': '000-111-2223', ' 003':'000-111-2224',}
    • 在我使用的 Python 版本中,字典中的任何重复键都会被覆盖(取决于键和 Python 版本)。您是否在 Python 中尝试过print({ '001': '000-111-2222', '001': '000-314-2222', '002': '000-111-2223', '003': '000-111-2224', })?如果确实显示两个键 001,请提供 Python 版本的链接并指定您使用的平台。如果它没有显示两个键 001,请说明您希望另一个值如何显示在 YAML 输出中,如果它不在传递给 dump 方法的数据中。
    • @Dirk YAML 不支持映射中的重复键(Python dicts 的正常表示),这就是为什么我至少要使用序列/列表。 IMO 最好为键 001 使用一些标记值来处理多个电话号码,但这比这里的 cmets 可以处理的要多:在这种情况下,发布一个新问题,清楚地描述你从哪里开始和结束- 你想要的结果。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-08-17
    • 1970-01-01
    • 2013-02-25
    相关资源
    最近更新 更多