8

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

line1.1
line1.2
line1.3

line2.1
line2.2

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

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

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

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

    列表(我的类(file_handle))

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

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

4

2 回答 2

7

蟒蛇2:

map(list, generator_of_generators)

蟒蛇 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]

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


由于在评论中你说你想避免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
  • 或者使用我将在稍后展示的黑暗的秘密黑客方法(我需要编写它),但不要这样做!

正如所承诺的那样,hack(对于 Python 3,这次是'round):

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。不过,你可以把它当作幽默来享受。

于 2013-09-26T16:37:48.113 回答
0

一种相当实用的方法是在创建时告诉“生成器的生成器”是生成生成器还是列表。虽然这不像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))
于 2013-09-26T17:37:19.027 回答