正如@flyx 所指出的,您应该查看ruamel.yaml 的往返功能(免责声明:我是该软件包的作者),即使没有内置的递归合并并且有一些注意事项。
首先,您应该引用您的 ? 值,否则您将收到不允许映射键的警告(因为普通的 ? 通常会引入明确定义的映射键)。
同样重要的是要知道ruamel.yaml 中的 cmets 的关联往往与评论之前的最后一个解析节点有关。因此,在您的 file2.yaml 中,# New Log File 注释与前面的键 db1 相关联,而不与下面的 logFile 相关联。
如果您愿意像这样输入file1.yaml:
project:
general:
environment: '?'
databases:
# Main Database
db1:
host: localhost
username: root
password: root123
dbname: project
logFile: '?'
和file2.yaml 喜欢:
project:
general:
environment: local
databases:
db1:
logFile: project.log # New Log File
那么这个程序:
import sys
from pathlib import Path
import ruamel.yaml
def update(d, n):
if isinstance(n, ruamel.yaml.comments.CommentedMap):
for k in n:
d[k] = update(d[k], n[k]) if k in d else n[k]
if k in n.ca._items and n.ca._items[k][2] and \
n.ca._items[k][2].value.strip():
d.ca._items[k] = n.ca._items[k] # copy non-empty comment
else:
d = n
return d
data1 = ruamel.yaml.round_trip_load(Path('file1.yaml').read_text())
update(data1, ruamel.yaml.round_trip_load(Path('file2.yaml').read_text()))
ruamel.yaml.round_trip_dump(data1, sys.stdout)
足以给你以下输出:
project:
general:
environment: local
databases:
# Main Database
db1:
host: localhost
username: root
password: root123
dbname: project
logFile: project.log # New Log File
请注意logFile: '?' 不必在file1.txt 中,因为缺少的键将在映射末尾添加。
如果将# New Log File 移动到密钥后的位置是不可接受的,那么您必须对从file2.yaml 加载的数据进行预处理,在这种情况下这并不难。这样做基于例如取决于原始file2.yaml 中的缩进是可能的,但是需要更多的代码行才能正确并且有点脆弱:
import sys
from pathlib import Path
import ruamel.yaml
INDENT=4
def update(d, n):
if isinstance(n, ruamel.yaml.comments.CommentedMap):
for k in n:
d[k] = update(d[k], n[k]) if k in d else n[k]
if k in n.ca._items and \
((n.ca._items[k][2] and n.ca._items[k][2].value.strip()) or \
n.ca._items[k][1]):
d.ca._items[k] = n.ca._items[k] # copy non-empty comment
else:
d = n
return d
def move_comment(d, depth=0):
# recursively adjust comment
if isinstance(d, ruamel.yaml.comments.CommentedMap):
for k in d:
if isinstance(d[k], ruamel.yaml.comments.CommentedMap):
if hasattr(d, 'ca'):
comment = d.ca.items.get(k)
if comment and comment[3] is not None:
# add to first key of the mapping that is the value
for k1 in d[k]:
d[k].yaml_set_comment_before_after_key(
k1,
before=comment[3][0].value.lstrip('#').strip(),
indent=INDENT*(depth+1))
break
move_comment(d[k], depth+1)
return d
data1 = ruamel.yaml.round_trip_load(Path('file1.yaml').read_text())
update(data1, move_comment(ruamel.yaml.round_trip_load(Path('file2.yaml').read_text())))
ruamel.yaml.round_trip_dump(data1, sys.stdout, indent=INDENT)
上面给出了完全你要求的输出,正确的 ('?') file1.yaml 和你原来的file2.yaml。