генератор генераторов питона?

Я написал класс, который читает текстовый файл. Файл состоит из блоков непустых строк (назовем их «разделами»), разделенных пустой строкой:

line1.1
line1.2
line1.3

line2.1
line2.2

Моей первой реализацией было чтение всего файла и возврат списка списков, то есть списка разделов, где каждый раздел представляет собой список строк. Это было очевидно ужасно с точки зрения памяти.

Поэтому я повторно реализовал его как генератор списков, то есть на каждом цикле мой класс считывает целый раздел в памяти в виде списка и выдает его.

Это лучше, но это все еще проблематично в случае больших разделов. Так что мне интересно, могу ли я переопределить его как генератор генераторов? Проблема в том, что этот класс очень универсальный, и он должен быть в состоянии удовлетворить оба этих варианта использования:

  1. прочитайте очень большой файл, содержащий очень большие разделы, и просматривайте его только один раз. Генератор генераторов идеально подходит для этого.
  2. считывает небольшой файл в память, чтобы циклически повторяться несколько раз. Генератор списков работает нормально, потому что пользователь может просто вызвать

    список (MyClass (file_handle))

Однако генератор генераторов НЕ будет работать в случае 2, поскольку внутренние объекты не будут преобразованы в списки.

Есть ли что-нибудь более элегантное, чем реализация явного метода to_list (), который превратил бы генератор генераторов в список списков?

7 голосов | спросил crusaderky 26 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowThu, 26 Sep 2013 20:14:21 +0400 2013, 20:14:21

2 ответа


0

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]

Если это не сработает, я понятия не имею, о чем вы спрашиваете. Кроме того, зачем возвращать функцию генератора, а не сам генератор?


Поскольку в комментариях вы сказали, что хотите избежать таинственного сбоя 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.

В основном, пожалуйста, не используйте этот хак. Вы можете наслаждаться этим как юмор, хотя.

ответил Veedrac 26 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowThu, 26 Sep 2013 20:37:48 +0400 2013, 20:37:48
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))
ответил tobias_k 26 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowThu, 26 Sep 2013 21:37:19 +0400 2013, 21:37:19

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132