【问题标题】:How to Yield in a recursive function in Python如何在 Python 中生成递归函数
【发布时间】:2018-10-09 16:33:36
【问题描述】:

所以我有一本字典:

{'a': {'b': {'c': 'd', 'e': 'f'}}}

我需要创建一个字典如下:

{'c':'d', 'e','f'}

它可以更深入到任何级别,但我应该始终以最大深度获取键值对。于是我写了一个函数:

def boil_down_array(key, data):
    if type(data) == dict:
        for key, item in data.items():
            boil_down_array(key, item)
    else:
        yield {key:data}

现在的问题是,一旦它进入递归,yield 就会丢失。我如何再次生成该字典?我得到的只是一个不是我想要的发电机。

【问题讨论】:

  • 但这会返回一个生成器对象而不是字典

标签: python python-3.x


【解决方案1】:

在递归调用中使用yield from,否则你只是忽略了递归调用的结果:

def boil_down_array(key, data):
    if type(data) == dict:
        for key, item in data.items():
            yield from boil_down_array(key, item)
    else:
        yield {key: data}

这仅在 Python > 3.3 中可用,但本质上只是简单地从额外循环中产生的简写:

for key, item in data.items():
    for x in boil_down_array(key, item):  # just exhaust the recursive generator
        yield x  # and "re-yield" what it produces

为了实现您想要的数据结构,您最好生成对而不是dicts,这样您就可以更轻松地将结果转换为生成的dict

yield key, data

然后你可以像这样使用它:

result = dict(boil_down_array(None, input_dict))

更简单的递归方法只会返回一个完整的dict

def boil_down_nested(dct):
    result = {}
    for k, v in dct.items():
        if isinstance(v, dict):
            result.update(boil_down_nested(v))
        else:
            result[k] = v
    return result

【讨论】:

    【解决方案2】:

    您忽略了递归调用产生的生成器对象:

    for key, item in data.items():
        boil_down_array(key, item)  # creates a generator object
    

    所以递归调用并没有实际执行(生成器中的代码永远不会为该调用执行)。

    您想使用 yield fromdelegate iteration 来调用:

    for key, item in data.items():
        yield from boil_down_array(key, item)
    

    yield from 将控制从当前生成器转移到 yield from 之后的表达式产生的迭代器;这是你的递归生成器。

    yield from 需要 Python 3.3 或更高版本。如果您使用的是 Python 2 或更早的 Python 3 版本,您还可以添加另一个循环来显式地生成迭代产生的每个结果:

    for key, item in data.items():
        for result in boil_down_array(key, item):
            yield result
    

    我也会使用isinstance(data, dict) 而不是type(...) ==,以允许子类:

    def boil_down_array(key, data):
        if isinstance(data, dict):
            for key, item in data.items():
                yield from boil_down_array(key, item)
        else:
            yield {key: data}
    

    请注意,您的代码实际上不会生成字典作为输出。它产生一个 iterable 的单键值字典:

    >>> d = {'a': {'b': {'c': 'd', 'e': 'f'}}}
    >>> list(boil_down_array('v', d))
    [{'c': 'd'}, {'e': 'f'}]
    

    最外层调用的 key 参数在这里也是多余的,因为您将其替换为当前迭代的键。

    如果您确实需要坚持使用生成器函数,那么至少生成 (key, value) 元组并且当 value 不是字典时不要打扰递归(因此在递归之前进行测试) , 消除传递密钥的需要;剩下的 data 参数现在被假定为字典,总是:

    def boil_down_nested(data):
        for key, value in data.items():
            if isinstance(value, dict):
                yield from boil_down_nested(value)
            else:
                yield (key, value)
    

    并使用 dict(boil_down_nested(input_dict)) 从生成器现在输出的键值元组生成一个新字典:

    >>> next(boil_down_nested(d))  # first resulting key-value pair
    ('c', 'd')
    >>> dict(boil_down_nested(d))  # all key-value pairs into a dictionary.
    {'c': 'd', 'e': 'f'}
    

    没有递归,您可以使用堆栈来跟踪嵌套字典仍要处理;这使得直接输出字典作为结果变得更加容易:

    def boil_down_nested_dict(d):
        stack = [d]
        output = {}
        while stack:
            for key, value in stack.pop().items():
                if isinstance(value, dict):
                    stack.append(value)  # process this value next
                else:
                    output[key] = value
        return output
    

    不再需要单独的dict() 呼叫:

    >>> boil_down_nested_dict(d)
    {'c': 'd', 'e': 'f'}
    

    【讨论】:

      【解决方案3】:

      请注意,您不一定需要使用yield

      def last(d):
        c = [i for b in d.items() for i in ([b] if not isinstance(b[-1], dict) else last(b[-1]))]
        return c
      
      print(dict(last({'a': {'b': {'c': 'd', 'e': 'f'}}})))
      

      输出:

      {'c': 'd', 'e': 'f'}
      

      【讨论】:

        猜你喜欢
        • 2012-01-14
        • 2016-04-03
        • 1970-01-01
        • 2011-04-23
        • 2019-04-27
        • 2016-06-01
        • 2015-05-14
        • 2023-03-24
        • 2021-12-24
        相关资源
        最近更新 更多