35 Вопрос: Какой самый «питонный» способ перебрать список по частям?

вопрос создан в Tue, May 23, 2017 12:00 AM

У меня есть скрипт на Python, который принимает в качестве входных данных список целых чисел, который мне нужен для работы с четырьмя целыми числами одновременно. К сожалению, у меня нет контроля над входом, или я бы передал его в виде списка из четырех элементов. В настоящее время я повторяю это следующим образом:

 
for i in xrange(0, len(ints), 4):
    # dummy op for example code
    foo += ints[i] * ints[i + 1] + ints[i + 2] * ints[i + 3]

Это похоже на «C-think», что заставляет меня подозревать, что есть более питонический способ справиться с этой ситуацией. Список отбрасывается после итерации, поэтому его не нужно сохранять. Возможно, что-то вроде этого будет лучше?

 
while ints:
    foo += ints[0] * ints[1] + ints[2] * ints[3]
    ints[0:4] = []

Тем не менее, все еще не совсем "чувствую" себя хорошо. : - /

Смежный вопрос: Как это сделать вы разбили список на куски одинакового размера в Python?

    
410
  1. Ваш код не работает, если размер списка не кратен четырем.
    2009-01-12 03: 03: 57Z
  2. Я расширяю () список так, чтобы его длина была кратна четырем, прежде чем он зайдет так далеко.
    2009-01-12 03: 44: 44Z
  3. @ ΤΖΩΤΖΙΟΥ - Вопросы очень похожи, но не совсем дублируют. Он «разделен на любое количество фрагментов размера N» против «разделен на N фрагментов любого размера». : -)
    2011-07-21 18: 16: 56Z
  4. 2012-06-23 15: 23: 43Z
  5. 2016-10-17 14: 05: 39Z
30 ответов                              30                         

Изменено в рецепте разделе Python itertools docs:

 
from itertools import zip_longest

def grouper(iterable, n, fillvalue=None):
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)

Пример сильный>
В псевдокоде, чтобы сохранить пример кратким.

 
grouper('ABCDEFG', 3, 'x') --> 'ABC' 'DEF' 'Gxx'

Примечание. в Python 2 используют izip_longest вместо zip_longest.

    
284
2019-03-25 19: 36: 26Z
  1. Наконец-то появилась возможность поиграть с этим в сеансе Python. Для тех, кто так же запутался, как и я, это отправляет один и тот же итератор в izip_longest несколько раз, заставляя его использовать последовательные значения одной и той же последовательности, а не чередующиеся значения из отдельных последовательностей. Я люблю это!
    2009-01-12 22: 00: 54Z
  2. Каков наилучший способ отфильтровать значение заполнения? ([элемент для элемента в элементах, если элемент не является значением заполнения] для элементов в группировщике (итерируемый))?
    2009-08-26 22: 48: 44Z
  3. Я не уверен, что это самый питонический ответ, но, возможно, это лучшее использование структуры [LIST]*n.
    2011-02-15 00: 01: 18Z
  4. Я подозреваю, что производительность этого рецепта группировщика для кусков размером 256 КБ будет очень низкой, потому что izip_longest будут получать аргументы 256 КБ.
    2013-04-28 15: 07: 45Z
  5. В некоторых местах комментаторы говорят «когда я наконец понял, как это работает ....» Может быть, требуется немного пояснений. В частности, список аспектов итераторов.
    2015-08-14 07: 00: 22Z
 
def chunker(seq, size):
    return (seq[pos:pos + size] for pos in range(0, len(seq), size))
# (in python 2 use xrange() instead of range() to avoid allocating a list)

Simple. Легко. Быстро. Работает с любой последовательностью:

 
text = "I am a very, very helpful text"

for group in chunker(text, 7):
   print repr(group),
# 'I am a ' 'very, v' 'ery hel' 'pful te' 'xt'

print '|'.join(chunker(text, 10))
# I am a ver|y, very he|lpful text

animals = ['cat', 'dog', 'rabbit', 'duck', 'bird', 'cow', 'gnu', 'fish']

for group in chunker(animals, 3):
    print group
# ['cat', 'dog', 'rabbit']
# ['duck', 'bird', 'cow']
# ['gnu', 'fish']
    
365
2018-10-19 15: 55: 23Z
  1. @ Версия Карлоса Красборна работает для любых итераций (не только для последовательностей, как в приведенном выше коде); это сжато и вероятно так же быстро или даже быстрее. Хотя это может быть немного неясным (неясным) для людей, незнакомых с модулем itertools.
    2009-01-12 14: 39: 37Z
  2. В 3.x мне нужно было только заменить xrange на range. См .: stackoverflow.com/a/15014576/671013
    2015-02-24 08: 23: 32Z
  3. Обратите внимание, что chunker возвращает generator. Замените возврат на: return [...], чтобы получить список.
    2015-02-24 08: 59: 57Z
  4. Вместо написания построения функции и последующего возврата генератора вы также можете написать генератор напрямую, используя yield: for pos in xrange(0, len(seq), size): yield seq[pos:pos + size]. Я не уверен, что внутренне это будет обрабатываться по-другому в любом соответствующем аспекте, но это может быть даже немного яснее.
    2016-04-15 10: 22: 29Z
  5. Обратите внимание, что это работает только для последовательностей, которые поддерживают доступ к элементам по индексу, и не будет работать для универсальных итераторов, поскольку они могут не поддерживать метод __getitem__.
    2017-12-22 18: 17: 27Z

Я фанат

 
chunk_size= 4
for i in range(0, len(ints), chunk_size):
    chunk = ints[i:i+chunk_size]
    # process chunk of size <= chunk_size
    
110
2019-03-26 18: 27: 49Z
  1. Как он себя ведет, если len (ints) не кратно chunkSize?
    2019-02-17 23: 15: 22Z
  2. @ AnnaVopureta chunk будет иметь 1, 2 или 3 элемента для последнего пакета элементов. См. Этот вопрос о том, почему индексы срезов могут выходить за пределы .
    2019-03-25 18: 56: 15Z
 
import itertools
def chunks(iterable,size):
    it = iter(iterable)
    chunk = tuple(itertools.islice(it,size))
    while chunk:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

# though this will throw ValueError if the length of ints
# isn't a multiple of four:
for x1,x2,x3,x4 in chunks(ints,4):
    foo += x1 + x2 + x3 + x4

for chunk in chunks(ints,4):
    foo += sum(chunk)

Другой способ:

 
import itertools
def chunks2(iterable,size,filler=None):
    it = itertools.chain(iterable,itertools.repeat(filler,size-1))
    chunk = tuple(itertools.islice(it,size))
    while len(chunk) == size:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

# x2, x3 and x4 could get the value 0 if the length is not
# a multiple of 4.
for x1,x2,x3,x4 in chunks2(ints,4,0):
    foo += x1 + x2 + x3 + x4
    
20
2009-01-12 03: 14: 04Z
  1. + 1 для использования генераторов, швы, как наиболее "питоническое" из всех предложенных решений
    2009-01-12 03: 23: 08Z
  2. Он довольно длинный и неуклюжий для чего-то такого легкого, что вовсе не очень питонично. Я предпочитаю версию С. Лотта
    2009-01-12 03: 51: 16Z
  3. @ zenazn: это будет работать на экземплярах генератора, нарезка не будет
    2012-11-25 17: 33: 14Z
  4. Помимо правильной работы с генераторами иДля других неслайсируемых итераторов первое решение также не требует значения «заполнитель», если конечный фрагмент меньше size, что иногда желательно.
    2014-08-19 20: 27: 24Z
  5. Также +1 для генераторов. Другие решения требуют вызова len и поэтому не работают на других генераторах.
    2015-04-10 17: 58: 07Z
 
from itertools import izip_longest

def chunker(iterable, chunksize, filler):
    return izip_longest(*[iter(iterable)]*chunksize, fillvalue=filler)
    
11
2009-01-12 14: 33: 36Z
  1. Удобный способ сделать это - stackoverflow.com/questions/434287/…
    2009-01-12 14: 29: 50Z

Мне нужно решение, которое бы также работало с наборами и генераторами. Я не мог придумать ничего очень короткого и красивого, но, по крайней мере, это вполне читабельно.

 
def chunker(seq, size):
    res = []
    for el in seq:
        res.append(el)
        if len(res) == size:
            yield res
            res = []
    if res:
        yield res

Список:

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

Set:

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

Генератор:

 
>>> list(chunker((i for i in range(10)), 3))
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
    
9
2013-08-14 23: 24: 51Z

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

 
it = iter([1, 2, 3, 4, 5, 6, 7, 8, 9])
for chunk in zip(it, it, it, it):
    print chunk

>>> (1, 2, 3, 4)
>>> (5, 6, 7, 8)

Таким образом, вы не получите последний частичный кусок. Если вы хотите получить (9, None, None, None) в качестве последнего чанка, просто используйте izip_longest из itertools.

    
8
2012-12-06 01: 56: 30Z
  1. можно улучшить с помощью zip(*([it]*4))
    2018-12-31 22: 32: 31Z
  2. @ Жан-Франсуа Фабр: с точки зрения читабельности я не вижу в этом улучшения. И это также немного медленнее. Это улучшение, если вы играете в гольф, а я нет.
    2019-01-02 01: 28: 56Z
  3. нет, я не играю в гольф, но что, если у вас есть 10 аргументов? Я прочитал эту конструкцию на какой-то официальной странице. Но, конечно, я не могу найти ее прямо сейчас:)
    2019-01-02 09: 13: 19Z
  4. @ Жан-Франсуа Фабр: если у меня 10 аргументов или переменное число аргументов, это вариант, но я бы лучше написал: zip (* (it ,) * 10)
    2019-01-02 11: 21: 24Z
  5. правильно! это , что я прочитал. не список того, что я составил:)
    2019-01-02 12: 35: 57Z

Идеальное решение этой проблемы работает с итераторами (а не только с последовательностями). Это также должно быть быстро.

Это решение, предоставляемое документацией для itertools:

 
def grouper(n, iterable, fillvalue=None):
    #"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return itertools.izip_longest(fillvalue=fillvalue, *args)

Используя ipython %timeit в эфире моей книги Mac, я получаю 47,5 нас за цикл.

Тем не менее, это действительно не работает для меня, так как результаты дополняются до групп равного размера. Решение без дополнения немного сложнее. Наиболее наивным решением может быть:

 
def grouper(size, iterable):
    i = iter(iterable)
    while True:
        out = []
        try:
            for _ in range(size):
                out.append(i.next())
        except StopIteration:
            yield out
            break

        yield out

Простой, но довольно медленный: 693 нас за цикл

Лучшее решение, которое я мог придумать, использует islice для внутреннего цикла:

 
def grouper(size, iterable):
    it = iter(iterable)
    while True:
        group = tuple(itertools.islice(it, None, size))
        if not group:
            break
        yield group

С тем же набором данных я получаю 305 нас за цикл.

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

 
def grouper(n, iterable, fillvalue=None):
    #"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    for i in itertools.izip_longest(fillvalue=fillvalue, *args):
        if tuple(i)[-1] == fillvalue:
            yield tuple(v for v in i if v != fillvalue)
        else:
            yield i

Мне действительно не нравится этот ответ, но он значительно быстрее. 124 доллара США за цикл

    
7
2012-05-29 00: 50: 20Z
  1. Вы можете сократить время выполнения рецепта № 3 на ~ 10-15%, переместив его в слой C (без импорта itertools; map должен быть Py3, map или imap): def grouper(n, it): return takewhile(bool, map(tuple, starmap(islice, repeat((iter(it), n))))). Ваша последняя функция может быть сделана менее хрупкой с помощью часового: избавьтесь от аргумента fillvalue; добавьте первую строку fillvalue = object(), затем измените проверку if на if i[-1] is fillvalue: и строку, которой она управляет, на yield tuple(v for v in i if v is not fillvalue). Гарантии, что никакое значение в iterable не может быть ошибочно принято за значение заполнителя.
    2016-09-30 01: 14: 07Z
  2. Кстати, большие пальцы на # 4. Я собирался опубликовать свою оптимизацию # 3 как лучший ответ (с точки зрения производительности), чем то, что было опубликовано до сих пор, но с настройкой, чтобы сделать его надежным, эластичный # 4 работает вдвое быстрее, чем оптимизированный # 3; Я не ожидал, что решение с петлями уровня Python (и без теоретических алгоритмических различий AFAICT) победит. Я предполагаю, что # 3 проигрывает из-за затрат на построение /итерацию объектов islice (# 3 выигрывает, если n относительно велика, например, количество групп мало, но это оптимизирует для необычного случая), но я не ожидал, что это будет довольно экстремальный.
    2016-09-30 01: 26: 02Z
  3. Для # 4 первая ветвь условного выражения берется только на последней итерации (последний кортеж). Вместо того, чтобы заново создавать окончательный кортеж, кешируйте по модулю длину исходного итерируемого вверху элемента и используйте его, чтобы вырезать нежелательные отступы с izip_longest на конечном кортеже: yield i[:modulo]. Кроме того, для переменной args введите ее вместо списка: args = (iter(iterable),) * n. Бреет еще несколько тактов. Наконец, если мы игнорируем значение заполнения и предполагаем None, условное выражение может стать if None in i для еще большего количества тактов.
    2017-08-14 20: 55: 53Z
  4. @ Kumba: Ваше первое предложение предполагает, что ввод имеет известную длину. Если это итератор /генератор, а не коллекция с известной длиной, то кешировать нечего. В любом случае, нет реальной причины использовать такую ​​оптимизацию; вы оптимизируете необычный случай (последний yield), в то время как общий случай не затронут.
    2017-11-13 19: 42: 59Z

Поскольку никто еще не упомянул об этом, вот решение zip():

 
>>> def chunker(iterable, chunksize):
...     return zip(*[iter(iterable)]*chunksize)

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

Пример: р>  

>>> s = '1234567890'
>>> chunker(s, 3)
[('1', '2', '3'), ('4', '5', '6'), ('7', '8', '9')]
>>> chunker(s, 4)
[('1', '2', '3', '4'), ('5', '6', '7', '8')]
>>> chunker(s, 5)
[('1', '2', '3', '4', '5'), ('6', '7', '8', '9', '0')]

Или с помощью itertools.izip вернуть итератор вместо списка:  

>>> from itertools import izip
>>> def chunker(iterable, chunksize):
...     return izip(*[iter(iterable)]*chunksize)

Заполнение можно исправить с помощью @ ΤΖΩΤΖΙΟΥ ответ :

 
>>> from itertools import chain, izip, repeat
>>> def chunker(iterable, chunksize, fillvalue=None):
...     it   = chain(iterable, repeat(fillvalue, chunksize-1))
...     args = [it] * chunksize
...     return izip(*args)
    
6
2017-05-23 12: 18: 14Z

Использование map () вместо zip () решает проблему заполнения в ответе Дж. Ф. Себастьяна:

 
>>> def chunker(iterable, chunksize):
...   return map(None,*[iter(iterable)]*chunksize)

Пример: р>  

>>> s = '1234567890'
>>> chunker(s, 3)
[('1', '2', '3'), ('4', '5', '6'), ('7', '8', '9'), ('0', None, None)]
>>> chunker(s, 4)
[('1', '2', '3', '4'), ('5', '6', '7', '8'), ('9', '0', None, None)]
>>> chunker(s, 5)
[('1', '2', '3', '4', '5'), ('6', '7', '8', '9', '0')]
    
5
2018-12-31 22: 31: 47Z
  1. Это лучше обрабатывать с помощью itertools.izip_longest (Py2) /itertools.zip_longest (Py3); такое использование map является устаревшим вдвойне и недоступно в Py3 (вы не можете передать None в качестве функции отображения, и оно останавливается, когда исчерпана самая короткая итерация, а не самая длинная; она не дополняется).
    2016-10-01 01: 34: 46Z

Если вы не возражаете против использования внешнего пакета, вы можете использовать iteration_utilities.grouper из iteration_utilties 1 . Он поддерживает все итерации (не только последовательности):

 
from iteration_utilities import grouper
seq = list(range(20))
for group in grouper(seq, 4):
    print(group)

который печатает:

 
(0, 1, 2, 3)
(4, 5, 6, 7)
(8, 9, 10, 11)
(12, 13, 14, 15)
(16, 17, 18, 19)

Если длина не кратна размеру группы, он также поддерживает заполнение (неполная последняя группа) или усечение (исключение неполной последней группы) последней:

 
from iteration_utilities import grouper
seq = list(range(17))
for group in grouper(seq, 4):
    print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)
# (16,)

for group in grouper(seq, 4, fillvalue=None):
    print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)
# (16, None, None, None)

for group in grouper(seq, 4, truncate=True):
    print(group)
# (0, 1, 2, 3)
# (4, 5, 6, 7)
# (8, 9, 10, 11)
# (12, 13, 14, 15)

1 Отказ от ответственности: я являюсь автором этого пакета.

    
4
2017-09-29 14: 29: 12Z

Если список большой, самый эффективный способ сделать это - использовать генератор:

 
def get_chunk(iterable, chunk_size):
    result = []
    for item in iterable:
        result.append(item)
        if len(result) == chunk_size:
            yield tuple(result)
            result = []
    if len(result) > 0:
        yield tuple(result)

for x in get_chunk([1,2,3,4,5,6,7,8,9,10], 3):
    print x

(1, 2, 3)
(4, 5, 6)
(7, 8, 9)
(10,)
    
3
2009-01-12 04: 17: 03Z
  1. (я думаю, что предложение itztools MizardX функционально эквивалентно этому.)
    2009-01-12 03: 40: 30Z
  2. (На самом деле, при отражении, нет, я не. itertools.islice возвращает итератор, но он не использует существующий.)
    2009-01-12 04: 15: 56Z
  3. Это красиво и просто, но по какой-то причине даже без преобразования в кортеж в 4-7 раз медленнее, чем принятый метод группирования на iterable = range(100000000) & chunksize до 10000.
    2018-10-08 08: 22: 50Z
  4. Однако в целом я бы рекомендовал этот метод, поскольку принятый метод может быть очень медленным, если проверка последнего элемента выполняется медленно docs.python.org/3/library/itertools.html#itertools.zip_longest
    2018-10-08 09: 51: 35Z

Использование маленьких функций и вещей на самом деле мне не нравится; Я предпочитаю просто использовать ломтики:

 
data = [...]
chunk_size = 10000 # or whatever
chunks = [data[i:i+chunk_size] for i in xrange(0,len(data),chunk_size)]
for chunk in chunks:
    ...
    
3
2013-02-21 10: 40: 10Z
  1. хорошо, но бесполезно для неопределенного потока, у которого нет известных len. Вы можете сделать тест с itertools.repeat или itertools.cycle.
    2014-04-24 09: 57: 22Z
  2. Кроме того, расходуется память из-за использования [...for...] понимание списка для физического создания списка вместо использования (...for...) выражение генератора , которое просто заботится о следующем элементе и свободной памяти
    2014-04-24 10: 00: 41Z

Другой подход заключается в использовании формы с двумя аргументами iter:

 
from itertools import islice

def group(it, size):
    it = iter(it)
    return iter(lambda: tuple(islice(it, size)), ())

Это можно легко адаптировать для использования отступов (это похоже на ответ Маркуса Жардеро ):

 
from itertools import islice, chain, repeat

def group_pad(it, size, pad=None):
    it = chain(iter(it), repeat(pad))
    return iter(lambda: tuple(islice(it, size)), (pad,) * size)

Их можно даже комбинировать для дополнительного заполнения:

 
_no_pad = object()
def group(it, size, pad=_no_pad):
    if pad == _no_pad:
        it = iter(it)
        sentinel = ()
    else:
        it = chain(iter(it), repeat(pad))
        sentinel = (pad,) * size
    return iter(lambda: tuple(islice(it, size)), sentinel)
    
2
2017-05-23 12: 34: 37Z
  1. предпочтительнее, поскольку у вас есть возможность опустить заполнение!
    2014-04-24 09: 50: 30Z

С NumPy это просто:

 
ints = array([1, 2, 3, 4, 5, 6, 7, 8])
for int1, int2 in ints.reshape(-1, 2):
    print(int1, int2)

выход: р>  

1 2
3 4
5 6
7 8
    
2
2014-11-19 04: 09: 38Z

Если я не пропустил что-то, следующее простое решение с выражениями генератора не было упомянуто. Предполагается, что известны как размер, так и количество блоков (что часто имеет место), и что заполнение не требуется:

 
def chunks(it, n, m):
    """Make an iterator over m first chunks of size n.
    """
    it = iter(it)
    # Chunks are presented as tuples.
    return (tuple(next(it) for _ in range(n)) for _ in range(m))
    
2
2019-03-27 16: 55: 34Z

В вашем втором методе я бы перешел к следующей группе из 4, выполнив следующее:

 
ints = ints[4:]

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

Сказав это, я обычно выбираю первый метод. Это не красиво, но это часто является следствием взаимодействия с внешним миром.

    
1
2009-01-12 02: 59: 16Z

Еще один ответ, преимуществами которого являются:

1) Легко понятно
2) Работает с любыми повторяемыми, а не только с последовательностями (некоторые из приведенных выше ответов захлебнутся файловыми дескрипторами)
3) не загружает чанк в память все сразу
4) не создает длинный список ссылок на один и тот же итератор в памяти
5) Заполнение значений заполнения в конце списка не допускается.

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

 
def chunkiter(iterable, size):
  def inneriter(first, iterator, size):
    yield first
    for _ in xrange(size - 1): 
      yield iterator.next()
  it = iter(iterable)
  while True:
    yield inneriter(it.next(), it, size)

In [2]: i = chunkiter('abcdefgh', 3)
In [3]: for ii in i:                                                
          for c in ii:
            print c,
          print ''
        ...:     
        a b c 
        d e f 
        g h 

Update:
Пара недостатков из-за того, что внутренний и внешний циклы извлекают значения из одного и того же итератора:
1) continue не работает должным образом во внешнем цикле - он просто переходит к следующему элементу, а не пропускает фрагмент. Однако это не выглядит проблемой, так как во внешнем цикле проверять нечего.
2) разрыв не работает так, как ожидалось во внутреннем цикле - элемент управления снова окажется во внутреннем цикле со следующим элементом в итераторе. Чтобы пропустить целые куски, либо оберните внутренний итератор (ii выше) в кортеж, например, for c in tuple(ii), или установите флаг и исчерпайте итератор.

    
1
2012-11-12 02: 22: 56Z
 
def group_by(iterable, size):
    """Group an iterable into lists that don't exceed the size given.

    >>> group_by([1,2,3,4,5], 2)
    [[1, 2], [3, 4], [5]]

    """
    sublist = []

    for index, item in enumerate(iterable):
        if index > 0 and index % size == 0:
            yield sublist
            sublist = []

        sublist.append(item)

    if sublist:
        yield sublist
    
1
2014-02-20 11: 45: 37Z
  1. + 1 это исключает заполнение; Ваши и bcoughlan очень похожи
    2014-04-24 09: 54: 59Z

Вы можете использовать раздел или chunks из funcy :

 
from funcy import partition

for a, b, c, d in partition(4, ints):
    foo += a * b * c * d

Эти функции также имеют версии итераторов ipartition и ichunks, которые в этом случае будут более эффективными.

Вы также можете посмотреть их реализацию . р>     

1
2014-06-04 20: 13: 23Z

Чтобы избежать всех преобразований в список import itertools и:

 
>>> for k, g in itertools.groupby(xrange(35), lambda x: x/10):
...     list(g)

Выдает:

 
... 
0 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1 [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
2 [20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
3 [30, 31, 32, 33, 34]
>>> 

Я проверил groupby, и он не конвертируется в список или не использует len, поэтому я (думаю) задержит разрешение каждого значения, пока оно не будет фактически использовано. К сожалению, ни один из доступных ответов (в настоящее время), казалось, не предлагал этот вариант.

Очевидно, что если вам нужно обрабатывать каждый элемент по очереди, вложите цикл for в g:

 
for k,g in itertools.groupby(xrange(35), lambda x: x/10):
    for i in g:
       # do what you need to do with individual items
    # now do what you need to do with the whole group

Мой особый интерес в этом заключался в необходимости использования генератора для отправки изменений в пакетах до 1000 в API gmail:

 
    messages = a_generator_which_would_not_be_smart_as_a_list
    for idx, batch in groupby(messages, lambda x: x/1000):
        batch_request = BatchHttpRequest()
        for message in batch:
            batch_request.add(self.service.users().messages().modify(userId='me', id=message['id'], body=msg_labels))
        http = httplib2.Http()
        self.credentials.authorize(http)
        batch_request.execute(http=http)
    
1
2014-10-18 08: 48: 46Z
  1. Что если список, который вы разбиваете на части, является чем-то отличным от последовательности восходящих целых чисел?
    2015-10-17 14: 33: 31Z
  2. @ PaulMcGuire see 2015-10-19 04: 55: 36Z
  3. Да, я знаком с groupby. Но если бы сообщения были буквами «ABCDEFG», то groupby(messages, lambda x: x/3) выдаст вам TypeError (для попытки разделить строку на int), а не 3-буквенные группировки. Теперь, если вы сделали groupby(enumerate(messages), lambda x: x[0]/3), у вас могло бы быть что-то. Но вы не сказали этого в своем посте.
    2015-10-19 21: 36: 20Z

О решении, предоставленном J.F. Sebastian здесь :

 
def chunker(iterable, chunksize):
    return zip(*[iter(iterable)]*chunksize)

Это умно, но имеет один недостаток - всегда возвращать кортеж. Как получить строку вместо?
Конечно, вы можете написать ''.join(chunker(...)), но временный кортеж все равно создан.

Вы можете избавиться от временного кортежа, написав собственный zip, например так:

 
class IteratorExhausted(Exception):
    pass

def translate_StopIteration(iterable, to=IteratorExhausted):
    for i in iterable:
        yield i
    raise to # StopIteration would get ignored because this is generator,
             # but custom exception can leave the generator.

def custom_zip(*iterables, reductor=tuple):
    iterators = tuple(map(translate_StopIteration, iterables))
    while True:
        try:
            yield reductor(next(i) for i in iterators)
        except IteratorExhausted: # when any of iterators get exhausted.
            break

Тогда

 
def chunker(data, size, reductor=tuple):
    return custom_zip(*[iter(data)]*size, reductor=reductor)

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

 
>>> for i in chunker('12345', 2):
...     print(repr(i))
...
('1', '2')
('3', '4')
>>> for i in chunker('12345', 2, ''.join):
...     print(repr(i))
...
'12'
'34'
    
1
2017-05-23 12: 18: 14Z
  1. Не критика для вас, чтобы изменить ваш ответ, а скорее комментарий: Кодекс является ответственностью. Чем больше кода вы пишете, тем больше места вы создаете для скрытия ошибок. С этой точки зрения переписывание zip вместо существующего кажется не лучшей идеей.
    2016-04-15 10: 32: 46Z

Мне нравится этот подход. Он простой и не магический, поддерживает все повторяемые типы и не требует импорта.

 
def chunk_iter(iterable, chunk_size):
it = iter(iterable)
while True:
    chunk = tuple(next(it) for _ in range(chunk_size))
    if not chunk:
        break
    yield chunk
    
1
2017-06-30 02: 05: 12Z

Я никогда не хочу, чтобы мои куски были дополнены, поэтому это требование является обязательным. Я считаю, что умение работать на любом итерируемом также является требованием. Учитывая это, я решил расширить принятый ответ, https://stackoverflow.com/a/434411/1074659 .

При таком подходе производительность незначительно падает, если заполнение не требуется из-за необходимости сравнивать и фильтровать дополненные значения. Однако для больших размеров блоков эта утилита очень эффективна.

 
#!/usr/bin/env python3
from itertools import zip_longest


_UNDEFINED = object()


def chunker(iterable, chunksize, fillvalue=_UNDEFINED):
    """
    Collect data into chunks and optionally pad it.

    Performance worsens as `chunksize` approaches 1.

    Inspired by:
        https://docs.python.org/3/library/itertools.html#itertools-recipes

    """
    args = [iter(iterable)] * chunksize
    chunks = zip_longest(*args, fillvalue=fillvalue)
    yield from (
        filter(lambda val: val is not _UNDEFINED, chunk)
        if chunk[-1] is _UNDEFINED
        else chunk
        for chunk in chunks
    ) if fillvalue is _UNDEFINED else chunks
    
1
2017-07-20 15: 20: 06Z
 
def chunker(iterable, n):
    """Yield iterable in chunk sizes.

    >>> chunks = chunker('ABCDEF', n=4)
    >>> chunks.next()
    ['A', 'B', 'C', 'D']
    >>> chunks.next()
    ['E', 'F']
    """
    it = iter(iterable)
    while True:
        chunk = []
        for i in range(n):
            try:
                chunk.append(next(it))
            except StopIteration:
                yield chunk
                raise StopIteration
        yield chunk

if __name__ == '__main__':
    import doctest

    doctest.testmod()
    
1
2019-03-20 11: 14: 58Z

Вот чанкер без импорта, который поддерживает генераторы:

 
def chunks(seq, size):
    it = iter(seq)
    while True:
        ret = tuple(next(it) for _ in range(size))
        if len(ret) == size:
            yield ret
        else:
            raise StopIteration()

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

 
>>> def foo():
...     i = 0
...     while True:
...         i += 1
...         yield i
...
>>> c = chunks(foo(), 3)
>>> c.next()
(1, 2, 3)
>>> c.next()
(4, 5, 6)
>>> list(chunks('abcdefg', 2))
[('a', 'b'), ('c', 'd'), ('e', 'f')]
    
1
2019-03-20 11: 15: 23Z

Похоже, что нет красивого способа сделать это. Здесь есть страница с несколькими методами, в том числе:

 
def split_seq(seq, size):
    newseq = []
    splitsize = 1.0/size*len(seq)
    for i in range(size):
        newseq.append(seq[int(round(i*splitsize)):int(round((i+1)*splitsize))])
    return newseq
    
0
2009-01-12 02: 56: 54Z

Если списки имеют одинаковый размер, вы можете объединить их в списки из 4-х кортежей с zip(). Например:

 
# Four lists of four elements each.

l1 = range(0, 4)
l2 = range(4, 8)
l3 = range(8, 12)
l4 = range(12, 16)

for i1, i2, i3, i4 in zip(l1, l2, l3, l4):
    ...

Вот что выдает функция zip():

 
>>> print l1
[0, 1, 2, 3]
>>> print l2
[4, 5, 6, 7]
>>> print l3
[8, 9, 10, 11]
>>> print l4
[12, 13, 14, 15]
>>> print zip(l1, l2, l3, l4)
[(0, 4, 8, 12), (1, 5, 9, 13), (2, 6, 10, 14), (3, 7, 11, 15)]

Если списки велики, и вы не хотите объединять их в больший список, используйте itertools.izip(), который создает итератор, а не список.

 
from itertools import izip

for i1, i2, i3, i4 in izip(l1, l2, l3, l4):
    ...
    
0
2009-01-12 03: 46: 12Z

Однострочное, временное решение для перебора списка x кусками размером 4 -

 
for a, b, c, d in zip(x[0::4], x[1::4], x[2::4], x[3::4]):
    ... do something with a, b, c and d ...
    
0
2014-09-01 12: 44: 40Z

Сначала я разработал его для разделения строк на подстроки для анализа строки, содержащей hex.
Сегодня я превратил его в сложный, но все же простой генератор.

 
def chunker(iterable, size, reductor, condition):
    it = iter(iterable)
    def chunk_generator():
        return (next(it) for _ in range(size))
    chunk = reductor(chunk_generator())
    while condition(chunk):
        yield chunk
        chunk = reductor(chunk_generator())

Аргументы:

Очевидные

  •  iterable - это любой итеративный /итератор /генератор, соединяющийся /генерирующий /итерирующий по входным данным,
  •  size - это, конечно, размер порции, которую вы хотите получить,

Более интересно

  • reductor - вызываемый объект, который получает генератор, перебирающий содержимое чанка.
    Я ожидаю, что он вернет последовательность или строку, но я этого не требую.

    Вы можете передать в качестве этого аргумента, например, list, tuple, set, frozenset,
    или что-нибудь более причудливое. Я бы передал эту функцию, возвращая строку
    (при условии, что iterable содержит /генерирует /перебирает строки):

     
    def concatenate(iterable):
        return ''.join(iterable)
    

    Обратите внимание, что reductor может вызвать закрытие генератора, вызвав исключение.

  • condition - вызываемый объект, который получает все, что вернул reductor.
    Он решает утвердить & вернуть его (вернув что-нибудь с оценкой True),
    или отклонить & завершить работу генератора (вернув что-нибудь другое или подняв исключение).

    Когда число элементов в iterable не делится на size, когда it исчерпывается, reductor получит генератор, генерирующий меньше элементов, чем size.
    Давайте назовем эти элементы lasts elements .

    Я предложил две функции для передачи в качестве этого аргумента:

    • lambda x:x - последние элементы будут возвращены.

    • lambda x: len(x)==<size> - последние элементы будут отклонены.
      замените <size> на число, равное size

0
2014-11-27 21: 18: 50Z
источник размещен Вот