【问题标题】:How can I add a comment to a YAML file in Python如何在 Python 中向 YAML 文件添加注释
【发布时间】:2015-09-08 18:08:15
【问题描述】:

我正在使用 https://pypi.python.org/pypi/ruamel.yaml 编写一个 YAML 文件

代码是这样的:

import ruamel.yaml
from ruamel.yaml.comments import CommentedSeq

d = {}
for m in ['B1', 'B2', 'B3']:
    d2 = {}
    for f in ['A1', 'A2', 'A3']:
        d2[f] = CommentedSeq(['test', 'test2'])
        if f != 'A2':
            d2[f].fa.set_flow_style()
    d[m] = d2

    with open('test.yml', "w") as f:
        ruamel.yaml.dump(
            d, f, Dumper=ruamel.yaml.RoundTripDumper,
            default_flow_style=False, width=50, indent=8)

我只想在顶部添加评论:

# Data for Class A

在 YAML 数据之前。

【问题讨论】:

    标签: python yaml ruamel.yaml


    【解决方案1】:

    在您的with 块内,您可以将任何您想要的内容写入文件。由于您只需要在顶部添加评论,请在调用 ruamel 之前添加对 f.write() 的调用:

    with open('test.yml', "w") as f:
        f.write('# Data for Class A\n')
        ruamel.yaml.dump(
            d, f, Dumper=ruamel.yaml.RoundTripDumper,
            default_flow_style=False, width=50, indent=8)
    

    【讨论】:

      【解决方案2】:

      原则上这是可能的,因为您可以往返此类“文件开头”的 cmets,但当前的 ruamel.yaml 0.10 并不能很好地支持它,而且在“从头开始”时肯定不会(即没有更改现有文件)。底部是一个简单且相对不错的解决方案,但我首先想介绍一个丑陋的解决方法以及如何逐步完成此操作。

      丑陋
      这样做的丑陋方法是在将 YAML 数据写入文件之前将注释添加到文件中。那就是插入:

      f.write('# Data for Class A\n')
      

      就在ruamel.yaml.dump(...)之前

      一步一步
      要在数据结构上插入注释,所以上面的 hack 是没有必要的,你首先 需要确保您的 d 数据是 CommentedMap 类型。如果 您可以通过将注释的 YAML 加载回 c 来比较 d 变量与具有注释的变量的区别

      import ruamel.yaml
      from ruamel.yaml.comments import Comment, CommentedSeq, CommentedMap
      
      d = CommentedMap()             # <<<<< most important
      for m in ['B1', 'B2', 'B3']:
          d2 = {}
          for f in ['A1', 'A2', 'A3']:
              d2[f] = CommentedSeq(['test', 'test2'])
              if f != 'A2':
                  d2[f].fa.set_flow_style()
          d[m] = d2
      
      yaml_str = ruamel.yaml.dump(d, Dumper=ruamel.yaml.RoundTripDumper,
                                  default_flow_style=False, width=50, indent=8)
      
      assert not hasattr(d, Comment.attrib)  # no attribute on the CommentedMap
      
      comment = 'Data for Class A'
      commented_yaml_str = '# ' + comment + '\n' + yaml_str
      c = ruamel.yaml.load(commented_yaml_str, Loader=ruamel.yaml.RoundTripLoader)
      assert hasattr(c, Comment.attrib)  # c has the attribute
      print c.ca                         # and this is what it looks like
      print d.ca                         # accessing comment attribute creates it empty
      assert hasattr(d, Comment.attrib)  # now the CommentedMap has the attribute
      

      打印出来:

      Comment(comment=[None, [CommentToken(value=u'# Data for Class A\n')]],
        items={})
      Comment(comment=None,
        items={})
      

      Comment 有一个属性 comment,需要将其设置为 2 元素列表,该列表由 EOL 注释(始终只有一个)和前一行 cmets 列表(以 CommentTokens 的形式)组成

      要创建一个 CommentToken,你需要一个(假的)StartMark 来告诉它从哪一列开始:

      from ruamel.yaml.error import StreamMark
      start_mark = StreamMark(None, None, None, 0, None, None)  # column 0
      

      现在您可以创建令牌了:

      from ruamel.yaml.tokens import CommentToken
      
      ct = CommentToken('# ' + comment + '\n', start_mark, None)
      

      将标记指定为 CommentedMap 上前面列表的第一个元素:

      d.ca.comment = [None, [ct]]
      print d.ca   # in case you want to check
      

      给你:

      Comment(comment=[None, [CommentToken(value='# Data for Class A\n')]],
        items={})
      

      最后:

      print ruamel.yaml.dump(d, Dumper=ruamel.yaml.RoundTripDumper)  
      

      给出:

      # Data for Class A
      B1:
              A1: [test, test2]
              A3: [test, test2]
              A2:
              - test
              - test2
      B2:
              A1: [test, test2]
              A3: [test, test2]
              A2:
              - test
              - test2
      B3:
              A1: [test, test2]
              A3: [test, test2]
              A2:
              - test
              - test2
      

      当然您不需要创建c 对象,这只是为了说明。

      你应该使用什么: 为了使整个练习更容易一些,您可以忘记细节并在以下方法中修补到CommentedBase一次:

      from ruamel.yaml.comments import CommentedBase
      
      def set_start_comment(self, comment, indent=0):
          """overwrites any preceding comment lines on an object
          expects comment to be without `#` and possible have mutlple lines
          """
          from ruamel.yaml.error import StreamMark
          from ruamel.yaml.tokens import CommentToken
          if self.ca.comment is None:
              pre_comments = []
              self.ca.comment = [None, pre_comments]
          else:
              pre_comments = self.ca.comments[1]
          if comment[-1] == '\n':
              comment = comment[:-1]  # strip final newline if there
          start_mark = StreamMark(None, None, None, indent, None, None)
          for com in comment.split('\n'):
              pre_comments.append(CommentToken('# ' + com + '\n', start_mark, None))
      
      if not hasattr(CommentedBase, 'set_start_comment'): # in case it is there
          CommentedBase.set_start_comment = set_start_comment
      

      然后就这样做:

      d.set_start_comment('Data for Class A')
      

      【讨论】:

      • 这似乎需要做很多工作,只是为了解决调用f.write() 的问题。有什么好处?也就是说,看起来它作为 ruamel 的内置部分是有意义的,或许可以向他们发送拉取请求?
      • @dimo414 是的,如果它仅用于文件的顶部,则它是开销。但是,如果您将其添加到数据中,然后使用列表中的数据并将其写出来,它就可以工作。我想解决这个问题,以通用方式包含在 ruamel.yaml 中,因为我是作者,所以不需要 PR。因此,它将在我上传到 PyPI (10.1) 的下一个更新中。然后我可以将答案减少到最后一行;-)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-09-20
      • 1970-01-01
      • 1970-01-01
      • 2013-06-17
      • 2013-04-11
      • 2022-06-23
      • 1970-01-01
      相关资源
      最近更新 更多