如何向 Python 中的 YAML 文件添加评论
How can I add a comment to a YAML file in Python
我正在使用 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 数据之前。
在您的 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)
这在原则上是可能的,因为你可以往返这样的 "start-of-file" 评论,但它在当前的 ruamel.yaml 0.10 中并没有得到很好的支持,当然在 "starting from scratch" (即不更改现有文件)。底部是一个简单且相对不错的解决方案,但我首先想介绍一个丑陋的解决方法以及如何逐步完成此操作。
丑:
执行此操作的丑陋方法是在向文件写入 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={})
A Comment
有一个属性 comment
需要设置为 2 元素列表,该列表由 EOL 注释(始终只有一个)和前一行注释列表(在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')
我正在使用 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 数据之前。
在您的 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)
这在原则上是可能的,因为你可以往返这样的 "start-of-file" 评论,但它在当前的 ruamel.yaml 0.10 中并没有得到很好的支持,当然在 "starting from scratch" (即不更改现有文件)。底部是一个简单且相对不错的解决方案,但我首先想介绍一个丑陋的解决方法以及如何逐步完成此操作。
丑:
执行此操作的丑陋方法是在向文件写入 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={})
A Comment
有一个属性 comment
需要设置为 2 元素列表,该列表由 EOL 注释(始终只有一个)和前一行注释列表(在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')