python: есть ли библиотечная функция для разделения входного потока?

Я хочу разделить входной поток для пакетной обработки. Учитывая входной список или генератор,

x_in = [1, 2, 3, 4, 5, 6 ...]

Мне нужна функция, которая будет возвращать куски этого ввода. Скажем, если chunk_size=4, то,

x_chunked = [[1, 2, 3, 4], [5, 6, ...], ...]

Это то, что я делаю снова и снова, и мне было интересно, есть ли более стандартный способ, чем писать это самому. Я что-то пропустил в itertools? (Можно было бы решить проблему с enumerate и groupby, но это кажется неуклюжим.) Если кто-то хочет увидеть реализацию, вот она,

def chunk_input_stream(input_stream, chunk_size):
    """partition a generator in a streaming fashion"""
    assert chunk_size >= 1
    accumulator = []
    for x in input_stream:
        accumulator.append(x)
        if len(accumulator) == chunk_size:
            yield accumulator
            accumulator = []
    if accumulator:
        yield accumulator

Редактировать

Вдохновленный ответом kreativitea, вот решение с islice, которое простое и не требует постфильтрации,

from itertools import islice

def chunk_input_stream(input_stream, chunk_size):
    while True:
        chunk = list(islice(input_stream, chunk_size))
        if chunk:
            yield chunk
        else:
            return

# test it with list(chunk_input_stream(iter([1, 2, 3, 4]), 3))

person gatoatigrado    schedule 05.11.2012    source источник


Ответы (3)


[Обновленная версия благодаря OP: я бросал yield from на все подряд с тех пор, как обновился, и мне даже не пришло в голову, что он мне здесь не нужен.]

О, какого черта:

from itertools import takewhile, islice, count

def chunk(stream, size):
    return takewhile(bool, (list(islice(stream, size)) for _ in count()))

который дает:

>>> list(chunk((i for i in range(3)), 3))
[[0, 1, 2]]
>>> list(chunk((i for i in range(6)), 3))
[[0, 1, 2], [3, 4, 5]]
>>> list(chunk((i for i in range(8)), 3))
[[0, 1, 2], [3, 4, 5], [6, 7]]

Предупреждение: вышеизложенное страдает той же проблемой, что и OP chunk_input_stream, если вход представляет собой список. Вы можете обойти это с помощью дополнительной iter() обмотки, но это менее красиво. Концептуально использование repeat или cycle может иметь больше смысла, чем count(), но по какой-то причине я считал символы. :^)

[FTR: нет, я все еще не совсем серьезно отношусь к этому, но эй-- это понедельник.]

person DSM    schedule 05.11.2012
comment
Вам не нужен yield from; это может отлично работать в Python 2.x, если вы просто return takewhile.... Внесите это редактирование, и я отмечу это как правильный ответ. Кроме того, вы можете включить строку импорта для полноты from itertools import takewhile, islice, count. Ваше решение лаконичное, на самом деле довольно простое (см. мои комментарии, почему у Джона нет) и работает. Спасибо!! - person gatoatigrado; 06.11.2012

Рецепт от itertools:

def grouper(n, iterable, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx
    args = [iter(iterable)] * n
    return izip_longest(fillvalue=fillvalue, *args)
person Jon Clements♦    schedule 05.11.2012
comment
И, естественно, обратите внимание на небольшую разницу zip_longest для 3.x. - person Gareth Latty; 06.11.2012
comment
Разве вы не можете использовать itertools.repeat вместо []*n? - person jpm; 06.11.2012
comment
Это потребует небольшой настройки для случая OP - код OP не заполняется, IIUC. - person DSM; 06.11.2012
comment
@DSM Затем отфильтруйте None или добавьте контрольное значение, если вам нужно None. - person Gareth Latty; 06.11.2012
comment
@jpm Поскольку все это будет сразу исчерпано в вызове zip_longest() в качестве аргументов, я полагаю, что накладные расходы от генератора сделают его медленнее, чем умножение списка. Этот способ проще и, возможно, быстрее. - person Gareth Latty; 06.11.2012
comment
Для моей конкретной цели мне нужно было бы отфильтровать None, но это не имеет большого значения. Это решение приятно. С другой стороны, однако, это немного умно — не сразу понятно, что происходит, и не ясно, можно ли что-то купить за ум. Если бы морской окунь был частью itertools, это было бы замечательно; поскольку это не так, я немного не решаюсь использовать его по сравнению с моим простым решением. - person gatoatigrado; 06.11.2012
comment
Чтобы уточнить то, что кажется умным, очень важно знать, что [iter(iterable)] * n != [iter(iterable), iter(iterable), ...]. Это не очевидно на первый взгляд. - person gatoatigrado; 06.11.2012
comment
@Lattyware Если я не совсем неправильно истолковал izip_longest (или zip_longest для 3.x), похоже, он не использует итерации до тех пор, пока не произойдет фактическая итерация. (Вот почему вы можете вызывать его с бесконечными итерациями, если позже вы не будете выполнять бесконечную итерацию.) - person jpm; 06.11.2012
comment
@gatoatigrado Если у вас есть это как функция, люди могут прочитать комментарий и увидеть имя функции. Это распространенный способ сделать это, и хотя он неочевиден, он понятен, прост и быстр, как только вы усвоите концепцию. - person Gareth Latty; 06.11.2012
comment
@jpm Нет, но генератор здесь (itertools.repeat(iter(iterable))) будет аргументами для izip_longest()/zip_longest и будет использоваться оператором * для создания аргументов для функции еще до ее запуска. - person Gareth Latty; 06.11.2012
comment
@Lattyware, TBH, ваш комментарий выглядит немного покровительственным и немного тупым, поскольку я использую строку документации в примере кода вопроса. Принимая во внимание это великодушно, я согласен, что имена и строки документации хороши для первой документации, но иногда мне даже быстрее читать код, если этот код очень понятен. Я повторю слова моего босса: приятно размещать умные части там, где они вам нужны, и пусть все остальное (например, эта вспомогательная функция) будет простым. Конечно, копирование стандартного решения обычно допустимо, но в других случаях, я думаю, простой код также менее глючный. - person gatoatigrado; 07.11.2012
comment
@gatoatigrado Не было намерения покровительствовать. Честно говоря, я действительно не вижу здесь решения, которое читалось бы лучше, чем это решение. - person Gareth Latty; 07.11.2012
comment
@Lattyware, извините за неправильное толкование. Если вы не заметили, я объяснил выше, почему мне не понравилось это решение. Я предполагаю, что мое предпочтение против этого решения частично связано с тем, что я провел некоторое время в языках FP, где можно с уверенностью предположить, что repeat 3 x совпадает с [x, x, x]. Python также имеет это для неизменяемых значений. Если бы решение было написано input_iterator = iter(iterable); args = [input_iter] * n, мне бы хотелось, чтобы оно было немного лучше. Но почему вам не нравится первое решение? Я думаю, что это даже более элегантно и точно отвечает на вопрос. - person gatoatigrado; 07.11.2012

Есть ли причина, по которой вы не используете что-то подобное?:

# data is your stream, n is your chunk length
[data[i:i+n] for i in xrange(0,len(data),n)]

изменить:

Так как люди делают генераторы....

def grouper(data, n):
    results = [data[i:i+n] for i in xrange(0,len(data),n)]
    for result in results:
        yield result

изменить 2:

Я подумал, что если у вас есть входной поток в памяти в виде двухсторонней очереди, вы можете .popleft очень эффективно получить n объектов.

from collections import deque
stream = deque(data)

def chunk(stream, n):
    """ Returns the next chunk from a data stream. """
    return [stream.popleft() for i in xrange(n)]

def chunks(stream, n, reps):
    """ If you want to yield more than one chunk. """
    for item in [chunk(stream, n) for i in xrange(reps)]:
        yield item
person kreativitea    schedule 05.11.2012
comment
Потоковое свойство действительно важно, когда вы имеете дело с большим количеством данных. ОТО, islice хорош... - person gatoatigrado; 06.11.2012
comment
Хороший. И с islice вам не нужно утверждать отрицательное значение, исключение встроено. - person kreativitea; 06.11.2012