【问题标题】:Puzzle: Recursive generator without loops/imports谜题:没有循环/导入的递归生成器
【发布时间】:2012-09-10 19:39:32
【问题描述】:

今天是发电机。我今天看到一个question,它想找到一种方法来递归地展平列表而不使用循环和导入。 tobias_k 回复了如下代码:

def flatten(test_list):
    if isinstance(test_list, list):
        if len(test_list) == 0:
            return []
        first, rest = test_list[0], test_list[1:]
        return flatten(first) + flatten(rest)
    else:
        return [test_list]

有没有办法创建一个生成器(保持规则:没有导入,循环)?

注意:这纯粹是教育性的。我知道这不是最好的主意,但不知道该怎么做。

【问题讨论】:

    标签: python generator


    【解决方案1】:

    generator function 是一个函数,它至少包含一个 yield statement 并且不包含采用表达式的 return 语句。当一个生成器函数被调用时,它返回一个生成器迭代器,它在迭代时(例如通过for 循环,或显式使用next)运行通过函数体,冻结其状态并将控制权返回给每个调用者yield 语句(在 Python 3.3 中,yield from 语句)。

    Python 函数中的流控制总是向前的;如果没有像设置当前帧f_lineno 那样的技巧(正如(愚人节)goto statement 所做的那样),控制达到较早点的唯一方法是使用循环(forwhile)。因此,如果没有循环或yield from,生成器迭代器可以被调用的最大次数受限于生成器函数中yield 语句的数量。

    请注意,编写返回迭代器的flatten 很容易;采用原始解决方案并编写return iter(flatten(first) + flatten(rest)) 就可以了。但这不会是生成器迭代器,函数也不会是生成器函数。

    这是一个滥用f_lineno 来提供无循环迭代的实现。不幸的是它必须使用import sys:

    def current_frame():
        i = None
        def gen():
            yield i.gi_frame.f_back
        i = gen()
        return next(i).f_back
    
    class Loop(object):
        jump = False
        def __call__(self, frame, event, arg):
            if self.jump:
                frame.f_lineno = self.lineno
                self.jump = False
            return None if event == 'call' else self
        def __enter__(self):
            import sys
            sys.settrace(self)
            current_frame().f_back.f_trace = self
            self.lineno = current_frame().f_back.f_lineno
            return self
        def __exit__(self, exc_type, exc_value, traceback):
            if exc_type is None:
                self.jump = True
            else:
                import sys
                sys.settrace(None)
                current_frame().f_back.f_trace = None
                return exc_type is StopIteration
    
    def flatten(x):
        if isinstance(x, list):
            if x:
                first, rest = flatten(x[0]), flatten(x[1:])
                with Loop():
                    yield next(first)
                with Loop():
                    yield next(rest)
                pass
        else:
            yield x
    

    【讨论】:

    • @ecatmur -- 这很好。感谢您的努力。我会尽量吸收这个。
    【解决方案2】:

    在 Python 3.3(开发版)中,您可以使用 yield from 构造来避免显式循环:

    def flatten(x):
        if isinstance(x, list):
            if x:
                first, rest = x[0], x[1:]
                yield from flatten(first)
                yield from flatten(rest)
        else:
            yield x
    

    在当前版本中,我想不出不使用itertools.chain的解决方案。

    【讨论】:

    • yield from 被实现为 C 循环。 :-P
    • 这并不能避免循环,它只是让解释器为你做循环的语法糖,不是吗?
    • @sr2222:当然。在两个列表上执行+ 也是如此。将“循环”更改为“显式循环”。
    • 就我而言,如何实现列表连接和yield from 完全无关紧要。从 python 的角度来看,这里没有循环......
    • @sr2222:如果你从字面上解释这个问题,是的。我假设要求是“没有列表推导,没有导入,并且没有循环在 Python 代码中”。
    【解决方案3】:

    用单行列表理解完成:

    def flatten (test_list):
        return [element for temp in test_list for element in flatten(temp)] if isinstance(test_list, list) else [test_list]
    
    
    print(flatten([1, [2, 1, [3, 6, 7]], [1, 2, [3, 2, 3], 4, [1, 2, 3, 4, 5]]]))
    #[1, 2, 1, 3, 6, 7, 1, 2, 3, 2, 3, 4, 1, 2, 3, 4, 5]
    

    【讨论】:

    • 当你遍历一个列表时,你必须使用一个循环。生成器和列表推导之间的区别在于推导保持循环关键字可见。你的意思是没有循环关键字?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-01-07
    • 2017-10-25
    • 1970-01-01
    • 1970-01-01
    • 2022-01-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多