如何使用 ruamel.yaml 自动转储嵌套字典中的修改值?
How to auto-dump modified values in nested dictionaries using ruamel.yaml?
当我尝试遵循解决方案 PyYAML - Saving data to .yaml files 并尝试使用 ruamel.yaml
修改嵌套字典中的值时
cfg = Config("test.yaml")
cfg['setup']['a'] = 3
print(cfg) # I can see the change for the `dict` but it is not saved
cfg['setup']['a']
值已更改,但未被 __setitem__()
捕获且未使用 updated()
函数保存。
是否可以自动转储嵌套 dict
中值的任何修改更改?
例如:
dict[in_key][out_key] = value
cfg['setup']['a'][b]['c'] = 3
PyYAML - Saving data to .yaml files:
class Config(dict):
def __init__(self, filename, auto_dump=True):
self.filename = filename
self.auto_dump = auto_dump
self.changed = False
self.yaml = YAML()
self.yaml.preserve_quotes = True
if os.path.isfile(filename):
with open(filename) as f:
super(Config, self).update(self.yaml.load(f) or {})
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 updated(self):
if self.auto_dump:
self.dump(force=True)
else:
self.changed = True
def __setitem__(self, key, value):
super(Config, self).__setitem__(key, value)
self.updated()
def update(self, *args, **kw):
for arg in args:
super(Config, self).update(arg)
super(Config, self).update(**kw)
self.updated()
相关:
您需要制作一个行为类似于 Config
的辅助 class SubConfig
。
在此之前摆脱旧样式 super(Config, self)
可能是个好主意。
更改__setitem__
以检查该值是否为字典,如果是
实例化 SubConfig
然后设置各个项目(
SubConfig 也需要这样做,所以你可以有任意嵌套)。
__init__
上的 SubConfig 不采用文件名,但采用
父级(Config
或 SubConfig
类型)。 Subconfig
本身不应该
dump,它的 updated
应该调用父 updated
(最终
冒泡到 Config
然后进行保存)。
为了支持 cfg['a'] = dict(c=1)
,您需要实施 __getitem__
,并且
类似于 del cfg['a']
实现 __delitem__
,使其写入更新的文件。
我认为你可以从另一个文件中提取class一个文件,因为几种方法是相同的,
但无法使其与 super()
一起正常工作。
如果您曾将列表分配给(嵌套的)键,并希望在更新元素时自动转储
在这样的列表中,您需要实现一些 SubConfigList
并处理 __setitem__
中的那些
import sys
import os
from pathlib import Path
import ruamel.yaml
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 = ruamel.yaml.representer.SafeRepresenter
_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 = ruamel.yaml.YAML(typ='safe')
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()
config_file = Path('config.yaml')
cfg = Config(config_file)
cfg['a'] = 1
cfg['b']['x'] = 2
cfg['c']['y']['z'] = 42
print(f'{config_file} 1:')
print(config_file.read_text())
cfg['b']['x'] = 3
cfg['a'] = 4
print(f'{config_file} 2:')
print(config_file.read_text())
cfg.update(a=9, d=196)
cfg['c']['y'].update(k=11, l=12)
print(f'{config_file} 3:')
print(config_file.read_text())
# reread config from file
cfg = Config(config_file)
assert isinstance(cfg['c']['y'], SubConfig)
assert cfg['c']['y']['z'] == 42
del cfg['c']
print(f'{config_file} 4:')
print(config_file.read_text())
# start from scratch immediately use updating
config_file.unlink()
cfg = Config(config_file)
cfg.update(a=dict(b=4))
cfg.update(c=dict(b=dict(e=5)))
assert isinstance(cfg['a'], SubConfig)
assert isinstance(cfg['c']['b'], SubConfig)
cfg['c']['b']['f'] = 22
print(f'{config_file} 5:')
print(config_file.read_text())
给出:
config.yaml 1:
a: 1
b:
x: 2
c:
y:
z: 42
config.yaml 2:
a: 4
b:
x: 3
c:
y:
z: 42
config.yaml 3:
a: 9
b:
x: 3
c:
y:
k: 11
l: 12
z: 42
d: 196
config.yaml 4:
a: 9
b:
x: 3
d: 196
config.yaml 5:
a:
b: 4
c:
b:
e: 5
f: 22
您应该考虑不要让这些 class 成为 dict
的子 class,而是将字典作为属性 ._d
(并替换 super().
和 self._d.
)。这将需要一个特定的代表 function/method.
这样做的好处是您不会意外地获得一些字典功能。例如。在上面的subclassing实现中,如果我没有实现__delitem__
,你仍然可以del cfg['c']
而不会出错,但是不会自动写入YAML文件。如果 dict 是一个属性,在你实现 __delitem__
.
之前你会得到一个错误
当我尝试遵循解决方案 PyYAML - Saving data to .yaml files 并尝试使用 ruamel.yaml
修改嵌套字典中的值时cfg = Config("test.yaml")
cfg['setup']['a'] = 3
print(cfg) # I can see the change for the `dict` but it is not saved
cfg['setup']['a']
值已更改,但未被 __setitem__()
捕获且未使用 updated()
函数保存。
是否可以自动转储嵌套 dict
中值的任何修改更改?
例如:
dict[in_key][out_key] = value
cfg['setup']['a'][b]['c'] = 3
PyYAML - Saving data to .yaml files:
class Config(dict): def __init__(self, filename, auto_dump=True): self.filename = filename self.auto_dump = auto_dump self.changed = False self.yaml = YAML() self.yaml.preserve_quotes = True if os.path.isfile(filename): with open(filename) as f: super(Config, self).update(self.yaml.load(f) or {}) 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 updated(self): if self.auto_dump: self.dump(force=True) else: self.changed = True def __setitem__(self, key, value): super(Config, self).__setitem__(key, value) self.updated() def update(self, *args, **kw): for arg in args: super(Config, self).update(arg) super(Config, self).update(**kw) self.updated()
相关:
您需要制作一个行为类似于 Config
的辅助 class SubConfig
。
在此之前摆脱旧样式 super(Config, self)
可能是个好主意。
更改__setitem__
以检查该值是否为字典,如果是
实例化 SubConfig
然后设置各个项目(
SubConfig 也需要这样做,所以你可以有任意嵌套)。
__init__
上的 SubConfig 不采用文件名,但采用
父级(Config
或 SubConfig
类型)。 Subconfig
本身不应该
dump,它的 updated
应该调用父 updated
(最终
冒泡到 Config
然后进行保存)。
为了支持 cfg['a'] = dict(c=1)
,您需要实施 __getitem__
,并且
类似于 del cfg['a']
实现 __delitem__
,使其写入更新的文件。
我认为你可以从另一个文件中提取class一个文件,因为几种方法是相同的,
但无法使其与 super()
一起正常工作。
如果您曾将列表分配给(嵌套的)键,并希望在更新元素时自动转储
在这样的列表中,您需要实现一些 SubConfigList
并处理 __setitem__
import sys
import os
from pathlib import Path
import ruamel.yaml
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 = ruamel.yaml.representer.SafeRepresenter
_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 = ruamel.yaml.YAML(typ='safe')
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()
config_file = Path('config.yaml')
cfg = Config(config_file)
cfg['a'] = 1
cfg['b']['x'] = 2
cfg['c']['y']['z'] = 42
print(f'{config_file} 1:')
print(config_file.read_text())
cfg['b']['x'] = 3
cfg['a'] = 4
print(f'{config_file} 2:')
print(config_file.read_text())
cfg.update(a=9, d=196)
cfg['c']['y'].update(k=11, l=12)
print(f'{config_file} 3:')
print(config_file.read_text())
# reread config from file
cfg = Config(config_file)
assert isinstance(cfg['c']['y'], SubConfig)
assert cfg['c']['y']['z'] == 42
del cfg['c']
print(f'{config_file} 4:')
print(config_file.read_text())
# start from scratch immediately use updating
config_file.unlink()
cfg = Config(config_file)
cfg.update(a=dict(b=4))
cfg.update(c=dict(b=dict(e=5)))
assert isinstance(cfg['a'], SubConfig)
assert isinstance(cfg['c']['b'], SubConfig)
cfg['c']['b']['f'] = 22
print(f'{config_file} 5:')
print(config_file.read_text())
给出:
config.yaml 1:
a: 1
b:
x: 2
c:
y:
z: 42
config.yaml 2:
a: 4
b:
x: 3
c:
y:
z: 42
config.yaml 3:
a: 9
b:
x: 3
c:
y:
k: 11
l: 12
z: 42
d: 196
config.yaml 4:
a: 9
b:
x: 3
d: 196
config.yaml 5:
a:
b: 4
c:
b:
e: 5
f: 22
您应该考虑不要让这些 class 成为 dict
的子 class,而是将字典作为属性 ._d
(并替换 super().
和 self._d.
)。这将需要一个特定的代表 function/method.
这样做的好处是您不会意外地获得一些字典功能。例如。在上面的subclassing实现中,如果我没有实现__delitem__
,你仍然可以del cfg['c']
而不会出错,但是不会自动写入YAML文件。如果 dict 是一个属性,在你实现 __delitem__
.