Как преобразовать итерируемое в поток? - PullRequest
11 голосов
/ 12 июля 2011

Если у меня есть итерация, содержащая строки, есть ли простой способ превратить его в поток?Я хочу сделать что-то вроде этого:

def make_file():
    yield "hello\n"
    yield "world\n"

output = tarfile.TarFile(…)
stream = iterable_to_stream(make_file())
output.addfile(…, stream)

Ответы [ 5 ]

25 голосов
/ 28 ноября 2013

Python 3 имеет новый API потока ввода-вывода ( библиотека документов ), заменивший старый файловый протокол объектов.(Новый API также доступен в Python 2 в модуле io и обратно совместим с файловым протоколом объектов.)

Вот реализация дляновый API в Python 2 и 3:

import io

def iterable_to_stream(iterable, buffer_size=io.DEFAULT_BUFFER_SIZE):
    """
    Lets you use an iterable (e.g. a generator) that yields bytestrings as a read-only
    input stream.

    The stream implements Python 3's newer I/O API (available in Python 2's io module).
    For efficiency, the stream is buffered.
    """
    class IterStream(io.RawIOBase):
        def __init__(self):
            self.leftover = None
        def readable(self):
            return True
        def readinto(self, b):
            try:
                l = len(b)  # We're supposed to return at most this much
                chunk = self.leftover or next(iterable)
                output, self.leftover = chunk[:l], chunk[l:]
                b[:len(output)] = output
                return len(output)
            except StopIteration:
                return 0    # indicate EOF
    return io.BufferedReader(IterStream(), buffer_size=buffer_size)

Пример использования:

with iterable_to_stream(str(x**2).encode('utf8') for x in range(11)) as s:
    print(s.read())
12 голосов
/ 12 июля 2011

Вот мой потоковый итератор - экспериментальная ветвь urllib3 , поддерживающая потоковый запрос по частям с помощью итераций:

class IterStreamer(object):
    """
    File-like streaming iterator.
    """
    def __init__(self, generator):
        self.generator = generator
        self.iterator = iter(generator)
        self.leftover = ''

    def __len__(self):
        return self.generator.__len__()

    def __iter__(self):
        return self.iterator

    def next(self):
        return self.iterator.next()

    def read(self, size):
        data = self.leftover
        count = len(self.leftover)

        if count < size:
            try:
                while count < size:
                    chunk = self.next()
                    data += chunk
                    count += len(chunk)
            except StopIteration:
                pass

        self.leftover = data[size:]

        return data[:size]

Источник с контекстом: https://github.com/shazow/urllib3/blob/filepost-stream/urllib3/filepost.py#L23

Связанные юнит-тесты: https://github.com/shazow/urllib3/blob/filepost-stream/test/test_filepost.py#L9

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

12 голосов
/ 12 июля 2011

Поскольку не похоже, что есть «стандартный» способ сделать это, я собрал простую реализацию:

class iter_to_stream(object):
    def __init__(self, iterable):
        self.buffered = ""
        self.iter = iter(iterable)

    def read(self, size):
        result = ""
        while size > 0:
            data = self.buffered or next(self.iter, None)
            self.buffered = ""
            if data is None:
                break
            size -= len(data)
            if size < 0:
                data, self.buffered = data[:size], data[size:]
            result += data
        return result
4 голосов
/ 12 июля 2011

Начальная точка:

class iterable_to_stream:
    def __init__(self, iterable):
        self.iter = iter(iterable)

    def read(self):
        try:
            return self.iter.next()
        except StopIteration:
            return ""
0 голосов
/ 12 июля 2011

TarFile принимает все, что обеспечивает файловый интерфейс - так что вы можете использовать StringIO (io.StringIO, если вы используете Python3.X) чтобы получить то, что вам нужно, TarFile.addfile() или вы можете создать свой собственный класс, который предоставляет файловый интерфейс и дает то, что вам нужно.

...