python - 如何使用 ruamel.yaml 自动转储嵌套字典中的修改值?
问题描述
当我尝试遵循解决方案PyYAML - 将数据保存到 .yaml 文件并尝试使用 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
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()
有关的:
解决方案
您将需要创建一个SubConfig
行为类似于Config
. super(Config, self)
在此之前摆脱旧样式可能是一个好主意。
更改__setitem__
以检查该值是否为 dict,如果是,则实例化SubConfig
然后设置各个项目(SubConfig 也需要这样做,因此您可以进行任意嵌套)。
子配置 on__init__
不采用文件名,但它采用父级(类型为Config
or SubConfig
)。Subconfig
本身不应该转储,它updated
应该调用父母updated
(最终冒泡Config
然后进行保存)。
为了支持cfg['a'] = dict(c=1)
你需要实现__getitem__
,和类似的del cfg['a']
实现__delitem__
,以使其写入更新的文件。
我认为您可以将一个文件从另一个文件子类化,因为几种方法是相同的,但无法使其正常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
您应该考虑不要使这些类成为 的子类dict
,而是将 dict 作为属性._d
(并替换super().
为self._d.
)。这将需要一个特定的表示函数/方法。
这样做的好处是您不会意外地获得一些 dict 功能。例如在上面的子类化实现中,如果我没有实现__delitem__
,你仍然可以del cfg['c']
没有错误,但不会自动写入 YAML 文件。如果 dict 是一个属性,你会得到一个错误,直到你实现__delitem__
.
推荐阅读
- javascript - createVNode 不是函数(Vue 与 Laravel)
- python - 返回自定义函数的布尔值
- javascript - 为什么在 Firebase Cloud Function 中没有成功完成 Promise.all() 的无服务器函数退出时性能会下降?
- javascript - 使用简单的箭头函数修复“一致返回”linter 问题
- r - 在 for 循环中附加 t.test 的问题
- python - 奇怪的函数调用和不一致的结果
- swiftui - iOS 在 iOS 13 中播放音频引发 C++ 异常,冻结应用
- python - 重新忽略某些行
- python - file_object.read() 在 python 3 中计算为空字符串
- python - 如何使用 CrawlSpider 动态设置“start_urls”