【问题标题】:Using recursion to string format YAML file使用递归字符串格式 YAML 文件
【发布时间】:2020-07-18 00:26:17
【问题描述】:

我有一个 yaml 文件,我将其转换为嵌套字典变量。字典被传递到一个 python 函数 (render_yaml_dict()) 中,该函数接受具有未格式化值的嵌套字典并返回具有格式化值的嵌套字典。我正在使用递归来解析嵌套字典和 jinja 格式,以使用兄弟值格式化字符串值。

这是函数的输入嵌套字典:

{'y': {'a1': 'foo', 'a2': 'bar{{a1}}', 'a3': {'b1': 'bird', 'b2': 'red{{b1}}'}},
 'x': {'c1': 'turtle'}}

这是函数的预期返回值:

{'y': {'a1': 'foo', 'a2': 'barfoo', 'a3': {'b1': 'bird', 'b2': 'redbird'}},
 'x': {'c1': 'turtle'}}

这是函数的实际返回值:

{'y': {'a1': 'foo', 'a2': 'barfoo', 'a3': {'b1': 'bird', 'b2': 'red{{b1}}'}},
 'x': {'c1': 'turtle'}}

问题

从上面的实际返回值可以看出,该函数没有格式化b2值,而是输出'red{{b1}}'的原始未格式化值。 a3b2 的直接父级,它被添加到 parent_list (应该会发生),但问题是 a3 也被添加到 visited 列表中,而没有任何明确的代码这样做(请参阅下面的render_yaml_dict() 函数中的### 问题:###)。因此,当render_yaml_dict() 检查 parent_list 是否在访问列表中时,'b2': 'red{{b1}}' 不会被传递到 jinja 模板代码中。如果您运行代码块,显示此问题的打印语句如下。

 stack list
[[('a1', 'foo'), ('a2', 'bar{{a1}}'), ('a3', {'b1': 'bird', 'b2': 'red{{b1}}'})]]

immediate_parent
a3
visited BEFORE appending immediate parent to parent_list
[['x'], ['y']]
parent_list BEFORE appending immediate parent to parent_list
['y']
parent_list AFTER appending immediate parent to parent_list
['y', 'a3']
visited AFTER appending immediate parent to parent_list
[['x'], ['y', 'a3']]

这是我目前拥有的 python 函数:

from functools import reduce
import operator
from yaml import safe_load
from jinja2 import Template, Undefined

def get_by_path(root, items):
    """Access a nested object in root by item sequence."""
    return reduce(operator.getitem, items, root)

def set_by_path(root, items, value):
    """Set a value in a nested object in root by item sequence."""
    get_by_path(root, items[:-1])[items[-1]] = value

def render_yaml_dict(nested_dict):
    # used for jinja formatting 
    class NullUndefined(Undefined):
        def __getattr__(self, key):
            return ''
    stack = list(nested_dict.items()) 
    visited = []
    parent_list = []
    while stack: 
        #dict used to store non nested key : value pairs
        not_dict = {}
        #dict used to store key : nested value pairs
        is_dict = {}
        #reset is_stack to false for every stack iteration
        is_stack = False
        immediate_parent = None
        print('\n \n stack list')
        print(stack)
        #pop last value in stack list
        current_stack = stack.pop()
        #if the current_stack is a tuple, put in list before converting to dict
        if isinstance(current_stack, tuple):
            stack_dict = dict([current_stack])
        else:
            stack_dict = dict(current_stack)
        #ordered list of parent keys that are parents of nested values
        for key,value in stack_dict.items():   
            #if value is a nested dictionary and the list of upstream parents haven't been chronologically visited
            if isinstance(value, dict) and parent_list not in visited:
                immediate_parent = key
                add_to_stack = list(value.items())
                is_dict.update({key:value})
                is_stack = True
            #if value isn't a nested dictionary and the list of upstream parents haven't been chronologically visited
            elif isinstance(value, dict) == False and parent_list not in visited:
                not_dict.update({key:value})
            else:
                continue

        # if key value pairs exist in not_dict dictionary
        if not_dict:
            #create duplicate dictionary of values to use for templating
            t = Template(str(not_dict), undefined=NullUndefined)
            c = safe_load(t.render())
            #format dictionary of values with template dictionary
            formatted_dict = safe_load(t.render(c))
            # add is_dict to formatted_dict because set_by_path() updates inplace
            formatted_dict = {**formatted_dict, **is_dict}
            # update nested_dict with formatted_dict
            set_by_path(nested_dict, parent_list, formatted_dict)
            # if there is only one parent node
            if len(parent_list) == 1:
                #append parent_list to visited
                visited.append(parent_list)
            elif len(parent_list) > 1:
                #append parent_list by don't include immediate parent
                visited.append(parent_list[:-1])
            else:
                pass
        #if there is atleast one key:nested value pair in current_stack
        if is_stack == True:
            ### PROBLEM:### The immediate_parent is added to the visited list even though there's no code that explicitly does
            print('\nimmediate_parent')
            print(immediate_parent)
            print('visited BEFORE appending immediate parent to parent_list')
            print(visited)
            print('parent_list BEFORE appending immediate parent to parent_list')
            print(parent_list)
            #add latest parent to parent_list
            parent_list.append(immediate_parent)  
            print('parent_list AFTER appending immediate parent to parent_list')
            print(parent_list)
            print('visited AFTER appending immediate parent to parent_list')
            print(visited)
            #add latest key: nested value pair to stack
            if len(add_to_stack) > 1:
                stack.append(add_to_stack)
            else:
                stack.extend(add_to_stack)
        #if there are no key: nested value pairs in current_stack, reset parent_list 
        else:
            if len(parent_list) > 1:
                del parent_list[-1]
            else:
                parent_list = []

    return nested_dict
config = {'y': {'a1': 'foo', 'a2': 'bar{{a1}}', 'a3': {'b1': 'bird', 'b2': 'red{{b1}}'}},
 'x': {'c1': 'turtle'}}
render_yaml_dict(config)

【问题讨论】:

  • 你为什么不使用像pyyaml这样的库?
  • @bigbounty 我目前正在查看pyyaml 文档。 pyyaml 是否有您必须传递的方法或参数以使用兄弟值格式化 yaml 值?
  • 为什么不看看这个库 - github.com/fabiocaccamo/python-benedict
  • @bigbounty 在 python benedict 中这个问题有什么用处?
  • 访问列表中没有添加任何内容。它之前包含两个元素,之后包含两个元素。修改后第二个元素的值发生了变化。

标签: python-3.x recursion yaml


【解决方案1】:

通过在 YAML 映射上注册自定义构造函数,这项工作似乎更容易实现:

import yaml
from yaml.resolver import *
from jinja2 import Template, Undefined

class NullUndefined(Undefined):
   def __getattr__(self, key):
     return ''

source = '''
{'y': {'a1': 'foo', 'a2': 'bar{{a1}}', 'a3': {'b1': 'bird', 'b2': 'red{{b1}}'}},
 'x': {'c1': 'turtle'}}
'''

def resolve_in_dict(loader, node):
  assert isinstance(node, yaml.MappingNode)
  values = loader.construct_mapping(node, deep=True)
  for key, value in values.items():
    if isinstance(value, str):
      t = Template(value, undefined=NullUndefined)
      values[key] = t.render(values)
  return values

yaml.SafeLoader.add_constructor(BaseResolver.DEFAULT_MAPPING_TAG, resolve_in_dict)

print(yaml.safe_load(source))

这将对 YAML 文件中的每个映射执行 resolve_in_dict(除了 !!map 之外没有显式标记)。它会生成您预期的输出。如果您使用 Jinja 变量引用字典,它们当然会被呈现为字符串,因为如果 b 引用字典,a{{b}} 还会发生什么?

【讨论】:

  • 这是一个简单得多的实现。非常感谢@flyx
猜你喜欢
  • 1970-01-01
  • 2023-02-24
  • 1970-01-01
  • 2021-12-11
  • 2018-12-13
  • 1970-01-01
  • 2017-07-20
  • 2012-04-17
相关资源
最近更新 更多