【问题标题】:python generator of generators?生成器的python生成器?
【发布时间】:2013-10-02 17:25:53
【问题描述】:

我写了一个读取 txt 文件的类。该文件由非空行块(我们称它们为“节”)组成,以空行分隔:

line1.1
line1.2
line1.3

line2.1
line2.2

我的第一个实现是读取整个文件并返回一个列表列表,即节列表,其中每个节都是行列表。 这在记忆方面显然很糟糕。

所以我将它重新实现为列表生成器,也就是说,在每个循环中,我的班级都会将内存中的整个部分作为列表读取并生成它。

这样比较好,但在大截面的情况下仍然存在问题。所以我想知道我是否可以将它重新实现为生成器的生成器?问题是这个类非常通用,它应该能够满足这两个用例:

  1. 读取一个非常大的文件,其中包含非常大的部分,并且只循环浏览一次。生成器的生成器非常适合此操作。
  2. 将一个小文件读入内存以循环多次。列表生成器工作正常,因为用户可以调用

    列表(MyClass(file_handle))

但是,生成器的生成器在情况 2 中不起作用,因为内部对象不会转换为列表。

有什么比实现显式 to_list() 方法更优雅的方法,它将生成器的生成器转换为列表列表?

【问题讨论】:

  • 您是否尝试过使用 readline。这样只读取一行行;由新行分隔。这是在内存中加载小数据的好方法,除非您的行本身很大。
  • @Vivek 我的行非常复杂,我从每行生成一个对象来验证行,其状态也取决于前面的行。向用户公开文件的内部格式不是一种选择。
  • 你能不能给一个示例输入行...
  • 问题到底是什么?假设你有生成器,如何编写生成器的生成器,或者如何为小文件创建列表列表?对于后一种情况:[list(section()) for section in MyClass(file_handle)] 呢?
  • @tobias_k 问题是如何从生成器的生成器创建列表列表,以一种对用户来说相当透明和优雅的方式。您的示例是我将如何实现我提到的显式 to_list() 方法,但我想知道是否有任何不需要用户调用显式 to_list() 方法的东西?换句话说,我想避免一旦分心的用户执行 list(MyClass(file_handle)),库就会神秘地崩溃。

标签: python list generator yield


【解决方案1】:

Python 2:

map(list, generator_of_generators)

Python 3:

list(map(list, generator_of_generators))

或两者兼而有之:

[list(gen) for gen in generator_of_generators]

由于生成的对象是 generator functions,而不仅仅是生成器,所以您会想要这样做

[list(gen()) for gen in generator_of_generator_functions]

如果这不起作用,我不知道你在问什么。另外,为什么它会返回生成器函数而不是生成器本身?


既然你在 cmets 中说你想避免 list(generator_of_generator_functions) 神秘地崩溃,这取决于你真正想要什么。

  • 不可能以这种方式覆盖list 的行为:要么存储子生成器元素,要么不存储

  • 如果确实发生了崩溃,我建议每次主生成器迭代时用主生成器循环耗尽子生成器。这是标准做法,也正是 itertools.groupby 所做的,一个标准库生成器。

例如。

def metagen():
    def innergen():
        yield 1
        yield 2
        yield 3

    for i in range(3):
        r = innergen()
        yield r

        for _ in r: pass
  • 或者使用我将在稍后展示的黑暗秘密黑客方法(我需要编写它),但不要这样做!

正如承诺的那样,破解(对于 Python 3,这次是“回合”):

from collections import UserList
from functools import partial


def objectitemcaller(key):
    def inner(*args, **kwargs):
        try:
            return getattr(object, key)(*args, **kwargs)
        except AttributeError:
            return NotImplemented
    return inner


class Listable(UserList):
    def __init__(self, iterator):
        self.iterator = iterator
        self.iterated = False

    def __iter__(self):
        return self

    def __next__(self):
        self.iterated = True
        return next(self.iterator)

    def _to_list_hack(self):
        self.data = list(self)
        del self.iterated
        del self.iterator
        self.__class__ = UserList

for key in UserList.__dict__.keys() - Listable.__dict__.keys():
    if key not in ["__class__", "__dict__", "__module__", "__subclasshook__"]:
        setattr(Listable, key, objectitemcaller(key))


def metagen():
    def innergen():
        yield 1
        yield 2
        yield 3

    for i in range(3):
        r = Listable(innergen())
        yield r

        if not r.iterated:
            r._to_list_hack()

        else:
            for item in r: pass

for item in metagen():
    print(item)
    print(list(item))
#>>> <Listable object at 0x7f46e4a4b850>
#>>> [1, 2, 3]
#>>> <Listable object at 0x7f46e4a4b950>
#>>> [1, 2, 3]
#>>> <Listable object at 0x7f46e4a4b990>
#>>> [1, 2, 3]

list(metagen())
#>>> [[1, 2, 3], [1, 2, 3], [1, 2, 3]]

太糟糕了,我什至不想解释。

关键是你有一个可以检测它是否被迭代的包装器,如果没有,你运行一个_to_list_hack,我不骗你,改变__class__属性。

由于布局冲突,我们必须使用 UserList 类并隐藏它的所有方法,这只是另一层杂物。

基本上,请不要使用此 hack。不过,你可以把它当作幽默来享受。

【讨论】:

    【解决方案2】:

    一种相当实用的方法是在创建时告诉“生成器的生成器”是生成生成器还是生成列表。虽然这不如让list 神奇地知道该做什么那么方便,但它似乎仍然比拥有一个特殊的to_list 功能更舒服。

    def gengen(n, listmode=False):
        for i in range(n):
            def gen():
                for k in range(i+1):
                    yield k
            yield list(gen()) if listmode else gen()
    

    根据listmode 参数,这可以用于生成生成器或列表。

    for gg in gengen(5, False):
        print gg, list(gg)
    print list(gengen(5, True))
    

    【讨论】:

      猜你喜欢
      • 2011-04-17
      • 2018-11-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-20
      • 2016-12-16
      • 1970-01-01
      • 2010-12-05
      • 1970-01-01
      相关资源
      最近更新 更多