python生成器?

2024-04-29 11:13:18 发布

您现在位置:Python中文网/ 问答频道 /正文

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

line1.1
line1.2
line1.3

line2.1
line2.2

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

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

这是更好的,但它仍然有问题的情况下,大段。所以我想知道我是否可以把它作为一个发电机的发电机重新实现?问题是这个类非常通用,它应该能够满足这两个用例:

  1. 阅读一个非常大的文件,包含非常大的部分,并且只循环浏览一次。一个发电机是完美的。
  2. 将一个小文件读入内存,以便多次循环。列表生成器工作正常,因为用户可以调用

    列表(MyClass(文件句柄))

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

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


Tags: 文件方法记忆内存用户txt列表myclass
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))

Python2:

map(list, generator_of_generators)

Python3:

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所做的,一个stdlib生成器。

例如

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

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

        for _ in r: pass
  • 或者使用一个黑暗的,秘密的黑客方法,我会在一个mo(我需要写),但不要这样做!

正如所承诺的,黑客(对于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类并隐藏其所有方法,这只是crud的另一层。

基本上,请不要使用这个黑客。不过,你可以把它当作幽默来欣赏。

相关问题 更多 >