以编程方式更新YAML文件
我有一个Python字典,它是通过读取YAML文件得到的,通常是这样的:
yaml.load(stream)
我想根据一个路径来程序化地更新这个YAML文件,路径的格式是:
group1,option1,option11,value
然后把更新后的字典再保存为YAML文件。现在我遇到的问题是,更新字典的时候,路径是动态的(比如说用户可以通过我用Cmd创建的简单命令行界面输入路径)。
有没有什么好的想法?
谢谢!
更新
让我更具体一点:问题在于更新字典的某一部分,而我事先并不知道它的结构。我正在做一个项目,所有的配置都存储在YAML文件中,我想添加一个命令行界面,这样就不用手动编辑这些文件了。下面是一个示例YAML文件,通过PyYaml加载到一个字典(config-dict)中:
config:
a-function: enable
b-function: disable
firewall:
NET:
A:
uplink: enable
downlink: enable
B:
uplink: enable
downlink: enable
subscriber-filter:
cancellation-timer: 180
service:
copy:
DS: enable
remark:
header-remark:
DSC: enable
remark-table:
port:
linkup-debounce: 300
p0:
mode: amode
p1:
mode: bmode
p2:
mode: amode
p3:
mode: bmode
我已经用Cmd创建了命令行界面,它运行得很好,甚至支持自动补全。用户可以输入类似这样的命令:
config port p1 mode amode
所以,我需要编辑:
config-dict['config']['port']['p1']['mode']并把它设置为'amode'。然后,使用yaml.dump()重新生成文件。另一个可能的命令是:
config a-function enable
所以config-dict['config']['a-function']必须设置为'enable'。
我遇到的问题是更新字典。如果Python是通过引用传递值,那就简单了:只需遍历字典,直到找到正确的值并保存它。实际上,这就是我为Cmd自动补全所做的。但是我不知道怎么进行更新。
希望我现在解释得更清楚了!
提前谢谢你。
4 个回答
试试这个方法,我用它来更新yaml或json文件。
def update_dictionary_recursively(dictionary, key, value, key_separator="."):
"""用给定的键和值更新指定的字典。
if dictionary contains value as dict E.g. {key1:value1, key2:{key3, {key4:value4}}} and you have to
update key4 then `key` should be given as `key2.key3.key4`.
If dictionary contains value as list E.g. {key1:{key2:[{key3:valie3}, {key4:value4}]}} and you have to update
key4 then `key` should be given as `key1.key2[1].key4`.
:param dictionary: Dictionary that is to be updated.
:type dictionary: dict
:param key: Key with which the dictionary is to be updated.
:type key: str
:param value: The value which will be used to update the key in dictionary.
:type value: object
:param key_separator: Separator with which key is separated.
:type key_separator str
:return: Return updated dictionary.
:rtype: dict
"""
index = key.find(key_separator)
if index != -1:
current_key = key[0:index]
key = key[index + 1:]
try:
if '[' in current_key:
key_index = current_key.split('[')
current_key = key_index[0]
list_index = int(key_index[1].strip(']'))
dictionary[current_key][list_index] = update_dictionary_recursively(
dictionary[current_key][list_index], key, value, key_separator)
else:
dictionary[current_key] = update_dictionary_recursively(dictionary[current_key],
key, value, key_separator)
except (KeyError, IndexError):
return dictionary
else:
if '[' in key:
key_index = key.split('[')
list_index = int(key_index[1].strip(']'))
if list_index > len(dictionary) - 1:
return dictionary
dictionary[list_index] = value
else:
if key not in dictionary:
return dictionary
dictionary[key] = value
return dictionary
`
更新似乎是pyyaml的一个短板。你甚至不能在以追加模式打开的文件上使用yaml.load,否则会抛出异常。对于复杂的字典来说,这可能有点麻烦,但如果每个添加的项目代表一个单独的案例或文档,你可以把它当作其他文本文件来处理。
newinfo = {"Pipi": {"score": 100000, "city": "Stockholm"}}
with open(fname, "a") as f:
sep = "\n" # For distinct documents use "\n...\n" as separator
# Pay attention to where you put the separator.
# if the file exists and is in traditional format place at
# beginning of string. else place at the end.
infostring = "{}".format(newinfo)
f.write(infostring + sep)
虽然这不一定能帮助你更新值,但它确实允许你更新文件。你也可以考虑使用json.dump来处理文件。我知道它是YAML格式,但这两种格式大体上是兼容的,除非你在YAML中使用了Python对象存储的功能。
为了在不同操作系统中处理换行符,记得使用os.linesep。
祝你好运!希望这些对你有帮助。
使用 python-benedict
来处理这个问题非常简单。它是一个很棒的 Python 字典子类,支持多种格式的输入输出操作,包括 yaml
格式。
安装方法:在命令行输入 pip install python-benedict
。
你可以直接从 yaml
文件初始化它:
from benedict import benedict
f = 'data.yaml'
d = benedict.from_yaml(f)
d['Pipi'] = {'score': 1000000, 'city': 'Stockholm'}
# benedict supports keypath (dot syntax by default),
# so it's possible to update nested values easily:
d['Pipi.score'] = 2000000
print(d['Pipi']) # -> {'score': 2000000, 'city': 'Stockholm'}
d.to_yaml(filepath=f)
这里是这个库的代码仓库和文档链接: https://github.com/fabiocaccamo/python-benedict
注意:我是这个项目的作者。
其实这个解决方案遵循一个简单的步骤:加载 - 修改 - 导出:
在开始之前,确保你已经安装了pyyaml这个库:
$ pip install pyyaml
testyaml.py
import yaml
fname = "data.yaml"
dct = {"Jan": {"score": 3, "city": "Karvina"}, "David": {"score": 33, "city": "Brno"}}
with open(fname, "w") as f:
yaml.dump(dct, f)
with open(fname) as f:
newdct = yaml.load(f)
print newdct
newdct["Pipi"] = {"score": 1000000, "city": "Stockholm"}
with open(fname, "w") as f:
yaml.dump(newdct, f)
生成的 data.yaml
$ cat data.yaml
David: {city: Brno, score: 33}
Jan: {city: Karvina, score: 3}
Pipi: {city: Stockholm, score: 1000000}