使用 ruamel.yaml 在嵌套字典中修改其值时如何保留 yaml 文件的注释
How can I keep comments of a yaml file when modify its values in nested dictionaries using ruamel.yaml
我在 的相关答案中使用解决方案,它使用默认(往返)loader/dumper。
我相信,这是一个难题,因为附加 dict
也应该保留评论。
=> 如果我们使用 modify values in nested dictionaries using ruamel.yaml
方法是否可以防止评论被删除?
示例:
config.yaml:
c: # my comment
b:
f: 5
a:
z: 4
b: 4 # my comment
代码(来自 How to auto-dump modified values in nested dictionaries using ruamel.yaml 的相同代码
),更改为使用默认值 (round-trip) loader/dumper
:
#!/usr/bin/env python3
from pathlib import Path
from ruamel.yaml import YAML, representer
class SubConfig(dict):
def __init__(self, parent):
self.parent = parent
def updated(self):
self.parent.updated()
def __setitem__(self, key, value):
if isinstance(value, dict):
v = SubConfig(self)
v.update(value)
value = v
super().__setitem__(key, value)
self.updated()
def __getitem__(self, key):
try:
res = super().__getitem__(key)
except KeyError:
super().__setitem__(key, SubConfig(self))
self.updated()
return super().__getitem__(key)
return res
def __delitem__(self, key):
res = super().__delitem__(key)
self.updated()
def update(self, *args, **kw):
for arg in args:
for k, v in arg.items():
self[k] = v
for k, v in kw.items():
self[k] = v
self.updated()
return
_SR = representer.RoundTripRepresenter
_SR.add_representer(SubConfig, _SR.represent_dict)
class Config(dict):
def __init__(self, filename, auto_dump=True):
self.filename = filename if hasattr(filename, "open") else Path(filename)
self.auto_dump = auto_dump
self.changed = False
self.yaml = YAML()
self.yaml.indent(mapping=4, sequence=4, offset=2)
self.yaml.default_flow_style = False
if self.filename.exists():
with open(filename) as f:
self.update(self.yaml.load(f) or {})
def updated(self):
if self.auto_dump:
self.dump(force=True)
else:
self.changed = True
def dump(self, force=False):
if not self.changed and not force:
return
with open(self.filename, "w") as f:
self.yaml.dump(dict(self), f)
self.changed = False
def __setitem__(self, key, value):
if isinstance(value, dict):
v = SubConfig(self)
v.update(value)
value = v
super().__setitem__(key, value)
self.updated()
def __getitem__(self, key):
try:
res = super().__getitem__(key)
except KeyError:
super().__setitem__(key, SubConfig(self))
self.updated()
return super().__getitem__(key)
def __delitem__(self, key):
res = super().__delitem__(key)
self.updated()
def update(self, *args, **kw):
for arg in args:
for k, v in arg.items():
self[k] = v
for k, v in kw.items():
self[k] = v
self.updated()
cfg = Config(Path("config.yaml"))
=> config.yaml
文件更新如下,删除了注释:
c:
b:
f: 5
a:
z: 4
b: 4
是的,可以防止评论丢失。在你的 Config.__init__()
方法中从 self.yaml.load()
返回的对象不是一个 dict,而是一个包含所有注释信息的子 class (ruamel.yaml.comments.CommentedMap
) (在它的 .ca
属性。并且 CommentedMap
将再次具有自身的值 CommentedMap
个实例(至少在您的输入中。
所以你需要做的是改变你的 classes:
class Config(ruamel.yaml.comments.CommentedMap):
并对 SubConfig
执行相同的操作。然后在 update
例程中,您应该尝试复制 .ca
属性(它将在 CommentedMap
上创建为空,但在 {}
上不可用)
确保你们都为 Config
添加了代表,并且 不要 在 Config.dump()
方法中转换为 dict
。
如果你也复制加载数据的 .fa
属性(也在 Subconfig
上),你将保留原始的 flow/block 风格,你可以做一个远离 self.yaml.default_flow_style = False
.
以上是理论,实践中还有几个问题。
您的 config.yaml
更改,尽管您没有明确转储。那是
因为您的 auto_dump
默认为 True。但这也意味着
每次更改都会转储,即您的 config.yaml
被转储 10 (ten) 次,而
Config
/SubConfig
数据结构得到构建。
如果 auto_dump
是 True
,我将其更改为仅转储一次,但即便如此我也不会
推荐,而不是仅在加载后更改时转储。
A dict
不需要初始化,但是 CommentedMap
需要。
如果你不这样做,你会在某个时候得到一个属性错误。所以你必须
在每个 class.
的 __init__
中调用 super().__init__(self)
from pathlib import Path
import ruamel.yaml
_SR = ruamel.yaml.representer.RoundTripRepresenter
class SubConfig(ruamel.yaml.comments.CommentedMap):
def __init__(self, parent):
self.parent = parent
super().__init__(self)
def updated(self):
self.parent.updated()
def __setitem__(self, key, value):
if isinstance(value, dict):
v = SubConfig(self)
v.update(value)
value = v
super().__setitem__(key, value)
self.updated()
def __getitem__(self, key):
try:
res = super().__getitem__(key)
except KeyError:
super().__setitem__(key, SubConfig(self))
self.updated()
return super().__getitem__(key)
return res
def __delitem__(self, key):
res = super().__delitem__(key)
self.updated()
def update(self, *args, **kw):
for arg in args:
for k, v in arg.items():
self[k] = v
for attr in [ruamel.yaml.comments.Comment.attrib, ruamel.yaml.comments.Format.attrib]:
if hasattr(arg, attr):
setattr(self, attr, getattr(arg, attr))
for k, v in kw.items():
self[k] = v
self.updated()
return
_SR.add_representer(SubConfig, _SR.represent_dict)
class Config(ruamel.yaml.comments.CommentedMap):
def __init__(self, filename, auto_dump=True):
super().__init__(self)
self.filename = filename if hasattr(filename, "open") else Path(filename)
self.auto_dump = False # postpone setting during loading of config
self.changed = False
self.yaml = ruamel.yaml.YAML()
self.yaml.indent(mapping=4, sequence=4, offset=2)
# self.yaml.default_flow_style = False
if self.filename.exists():
with open(filename) as f:
self.update(self.yaml.load(f) or {})
self.auto_dump = auto_dump
if auto_dump:
self.dump()
def updated(self):
if self.auto_dump:
self.dump(force=True)
else:
self.changed = True
def dump(self, force=False):
if not self.changed and not force:
return
# use the capability of dump to take a Path. It will open the file 'wb' as
# is appropriate for a YAML file, which is UTF-8
self.yaml.dump(self, self.filename)
self.changed = False
def __setitem__(self, key, value):
if isinstance(value, dict):
v = SubConfig(self)
v.update(value)
value = v
super().__setitem__(key, value)
self.updated()
def __getitem__(self, key):
try:
res = super().__getitem__(key)
except KeyError:
super().__setitem__(key, SubConfig(self))
self.updated()
return super().__getitem__(key)
def __delitem__(self, key):
res = super().__delitem__(key)
self.updated()
def update(self, *args, **kw):
for arg in args:
for k, v in arg.items():
self[k] = v
for attr in [ruamel.yaml.comments.Comment.attrib, ruamel.yaml.comments.Format.attrib]:
if hasattr(arg, attr):
setattr(self, attr, getattr(arg, attr))
for k, v in kw.items():
self[k] = v
self.updated()
_SR.add_representer(Config, _SR.represent_dict)
fn = Path('config.yaml')
fn.write_text("""
c: # my comment
b:
f: 5
x: {g: 6}
a:
z: 4
b: 4 # my comment
""")
cfg = Config(fn)
print(Path(fn).read_text())
给出:
c: # my comment
b:
f: 5
x: {g: 6}
a:
z: 4
b: 4 # my comment
由于输入发生变化,我编写了配置文件以在每个 运行 上进行测试。
我还添加了一个流式字典,以明确执行原始格式。
我在
我相信,这是一个难题,因为附加 dict
也应该保留评论。
=> 如果我们使用 modify values in nested dictionaries using ruamel.yaml
方法是否可以防止评论被删除?
示例:
config.yaml:
c: # my comment
b:
f: 5
a:
z: 4
b: 4 # my comment
代码(来自 How to auto-dump modified values in nested dictionaries using ruamel.yaml 的相同代码
),更改为使用默认值 (round-trip) loader/dumper
:
#!/usr/bin/env python3
from pathlib import Path
from ruamel.yaml import YAML, representer
class SubConfig(dict):
def __init__(self, parent):
self.parent = parent
def updated(self):
self.parent.updated()
def __setitem__(self, key, value):
if isinstance(value, dict):
v = SubConfig(self)
v.update(value)
value = v
super().__setitem__(key, value)
self.updated()
def __getitem__(self, key):
try:
res = super().__getitem__(key)
except KeyError:
super().__setitem__(key, SubConfig(self))
self.updated()
return super().__getitem__(key)
return res
def __delitem__(self, key):
res = super().__delitem__(key)
self.updated()
def update(self, *args, **kw):
for arg in args:
for k, v in arg.items():
self[k] = v
for k, v in kw.items():
self[k] = v
self.updated()
return
_SR = representer.RoundTripRepresenter
_SR.add_representer(SubConfig, _SR.represent_dict)
class Config(dict):
def __init__(self, filename, auto_dump=True):
self.filename = filename if hasattr(filename, "open") else Path(filename)
self.auto_dump = auto_dump
self.changed = False
self.yaml = YAML()
self.yaml.indent(mapping=4, sequence=4, offset=2)
self.yaml.default_flow_style = False
if self.filename.exists():
with open(filename) as f:
self.update(self.yaml.load(f) or {})
def updated(self):
if self.auto_dump:
self.dump(force=True)
else:
self.changed = True
def dump(self, force=False):
if not self.changed and not force:
return
with open(self.filename, "w") as f:
self.yaml.dump(dict(self), f)
self.changed = False
def __setitem__(self, key, value):
if isinstance(value, dict):
v = SubConfig(self)
v.update(value)
value = v
super().__setitem__(key, value)
self.updated()
def __getitem__(self, key):
try:
res = super().__getitem__(key)
except KeyError:
super().__setitem__(key, SubConfig(self))
self.updated()
return super().__getitem__(key)
def __delitem__(self, key):
res = super().__delitem__(key)
self.updated()
def update(self, *args, **kw):
for arg in args:
for k, v in arg.items():
self[k] = v
for k, v in kw.items():
self[k] = v
self.updated()
cfg = Config(Path("config.yaml"))
=> config.yaml
文件更新如下,删除了注释:
c:
b:
f: 5
a:
z: 4
b: 4
是的,可以防止评论丢失。在你的 Config.__init__()
方法中从 self.yaml.load()
返回的对象不是一个 dict,而是一个包含所有注释信息的子 class (ruamel.yaml.comments.CommentedMap
) (在它的 .ca
属性。并且 CommentedMap
将再次具有自身的值 CommentedMap
个实例(至少在您的输入中。
所以你需要做的是改变你的 classes:
class Config(ruamel.yaml.comments.CommentedMap):
并对 SubConfig
执行相同的操作。然后在 update
例程中,您应该尝试复制 .ca
属性(它将在 CommentedMap
上创建为空,但在 {}
上不可用)
确保你们都为 Config
添加了代表,并且 不要 在 Config.dump()
方法中转换为 dict
。
如果你也复制加载数据的 .fa
属性(也在 Subconfig
上),你将保留原始的 flow/block 风格,你可以做一个远离 self.yaml.default_flow_style = False
.
以上是理论,实践中还有几个问题。
您的 config.yaml
更改,尽管您没有明确转储。那是
因为您的 auto_dump
默认为 True。但这也意味着
每次更改都会转储,即您的 config.yaml
被转储 10 (ten) 次,而
Config
/SubConfig
数据结构得到构建。
如果 auto_dump
是 True
,我将其更改为仅转储一次,但即便如此我也不会
推荐,而不是仅在加载后更改时转储。
A dict
不需要初始化,但是 CommentedMap
需要。
如果你不这样做,你会在某个时候得到一个属性错误。所以你必须
在每个 class.
__init__
中调用 super().__init__(self)
from pathlib import Path
import ruamel.yaml
_SR = ruamel.yaml.representer.RoundTripRepresenter
class SubConfig(ruamel.yaml.comments.CommentedMap):
def __init__(self, parent):
self.parent = parent
super().__init__(self)
def updated(self):
self.parent.updated()
def __setitem__(self, key, value):
if isinstance(value, dict):
v = SubConfig(self)
v.update(value)
value = v
super().__setitem__(key, value)
self.updated()
def __getitem__(self, key):
try:
res = super().__getitem__(key)
except KeyError:
super().__setitem__(key, SubConfig(self))
self.updated()
return super().__getitem__(key)
return res
def __delitem__(self, key):
res = super().__delitem__(key)
self.updated()
def update(self, *args, **kw):
for arg in args:
for k, v in arg.items():
self[k] = v
for attr in [ruamel.yaml.comments.Comment.attrib, ruamel.yaml.comments.Format.attrib]:
if hasattr(arg, attr):
setattr(self, attr, getattr(arg, attr))
for k, v in kw.items():
self[k] = v
self.updated()
return
_SR.add_representer(SubConfig, _SR.represent_dict)
class Config(ruamel.yaml.comments.CommentedMap):
def __init__(self, filename, auto_dump=True):
super().__init__(self)
self.filename = filename if hasattr(filename, "open") else Path(filename)
self.auto_dump = False # postpone setting during loading of config
self.changed = False
self.yaml = ruamel.yaml.YAML()
self.yaml.indent(mapping=4, sequence=4, offset=2)
# self.yaml.default_flow_style = False
if self.filename.exists():
with open(filename) as f:
self.update(self.yaml.load(f) or {})
self.auto_dump = auto_dump
if auto_dump:
self.dump()
def updated(self):
if self.auto_dump:
self.dump(force=True)
else:
self.changed = True
def dump(self, force=False):
if not self.changed and not force:
return
# use the capability of dump to take a Path. It will open the file 'wb' as
# is appropriate for a YAML file, which is UTF-8
self.yaml.dump(self, self.filename)
self.changed = False
def __setitem__(self, key, value):
if isinstance(value, dict):
v = SubConfig(self)
v.update(value)
value = v
super().__setitem__(key, value)
self.updated()
def __getitem__(self, key):
try:
res = super().__getitem__(key)
except KeyError:
super().__setitem__(key, SubConfig(self))
self.updated()
return super().__getitem__(key)
def __delitem__(self, key):
res = super().__delitem__(key)
self.updated()
def update(self, *args, **kw):
for arg in args:
for k, v in arg.items():
self[k] = v
for attr in [ruamel.yaml.comments.Comment.attrib, ruamel.yaml.comments.Format.attrib]:
if hasattr(arg, attr):
setattr(self, attr, getattr(arg, attr))
for k, v in kw.items():
self[k] = v
self.updated()
_SR.add_representer(Config, _SR.represent_dict)
fn = Path('config.yaml')
fn.write_text("""
c: # my comment
b:
f: 5
x: {g: 6}
a:
z: 4
b: 4 # my comment
""")
cfg = Config(fn)
print(Path(fn).read_text())
给出:
c: # my comment
b:
f: 5
x: {g: 6}
a:
z: 4
b: 4 # my comment
由于输入发生变化,我编写了配置文件以在每个 运行 上进行测试。 我还添加了一个流式字典,以明确执行原始格式。