청크 단위로 목록을 반복하는 가장 "pythonic"방법은 무엇입니까?


487

정수 목록을 입력으로 사용하는 Python 스크립트가 있는데 한 번에 4 개의 정수로 작업해야합니다. 불행히도 입력을 제어 할 수 없거나 4 요소 튜플 목록으로 전달했습니다. 현재이 방법으로 반복하고 있습니다.

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] = []

그래도 여전히 "느낌"이 아닙니다. :-/

관련 질문 : 파이썬에서 어떻게 목록을 고른 크기의 덩어리로 나눕니 까?


3
목록 크기가 4의 배수가 아닌 경우 코드가 작동하지 않습니다.
Pedro Henriques

5
길이가 멀어지기 전에 4의 배수가되도록 목록을 extend ()하고 있습니다.
Ben Blank

4
@ ΤΖΩΤΖΙΟΥ — 질문은 매우 비슷하지만 그다지 중복되지는 않습니다. "N 크기의 청크에 분할"대 "모든 크기의 N 청크에 분할"입니다. :-)
Ben Blank


답변:


339

파이썬 itertools 문서 의 레시피 섹션 에서 수정되었습니다 .

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.


67
마침내 파이썬 세션 에서이 문제를 해결할 수있는 기회를 얻었습니다. 내가 혼란스러워하는 사람들에게는 동일한 반복자를 izip_longest에 여러 번 공급하여 별도의 시퀀스의 스트라이프 값 대신 동일한 시퀀스의 연속 값을 소비합니다. 나는 그것을 좋아한다!
Ben Blank

6
채우기 값을 걸러내는 가장 좋은 방법은 무엇입니까? (그룹화 항목 (iterable)의 항목에 대해 [항목이 채우기 값이 아닌 경우 항목의 항목])?
gotgenes

14
256k 크기의 청크에 대한이 그룹화 레시피의 성능은 izip_longest256k 인수가 제공 되므로 매우 열악 할 것으로 생각 됩니다.
anatoly techtonik

13
여러 곳에서 주석가들은 "이것이 어떻게 작동했는지 마침내 알게되었을 때 ..."라고 말합니다. 아마도 약간의 설명이 필요할 것입니다. 특히 반복자 목록 측면.
LondonRob

6
None마지막 덩어리를 채우지 않고 이것을 사용할 수 있습니까?
CMCDragonkai

420
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)

단순한. 쉬운. 빠른. 모든 시퀀스에서 작동합니다.

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']

16
@Carlos Crasborn의 버전은 모든 반복 가능 (위 코드와 같은 시퀀스뿐만 아니라)에서도 작동합니다. 간결하고 아마도 더 빠르거나 더 빠릅니다. itertools모듈에 익숙하지 않은 사람들에게는 다소 모호 할 수 있지만 명확하지 않을 수 있습니다 .
jfs

1
동의했다. 이것은 가장 일반적이고 파이썬적인 방법입니다. 명확하고 간결합니다. (및 앱 엔진에서 작동)
Matt Williamson

3
chunker는 을 반환합니다 generator. return [...]목록을 가져 오려면 다음 으로 돌아가십시오 .
Dror

11
대신 기능 건물을 작성하고 발전기를 반환하는, 당신은 또한 사용하여, 직접 발전기를 쓸 수있다 yield: for pos in xrange(0, len(seq), size): yield seq[pos:pos + size]. 내부적으로 이것이 관련 측면에서 다르게 처리되는지 확실하지 않지만 조금 더 명확 할 수 있습니다.
Alfe

3
이 방법은 인덱스로 항목 액세스를 지원하는 시퀀스에만 적용되며 __getitem__메소드를 지원하지 않을 수 있으므로 일반 반복자에서는 작동하지 않습니다 .
apollov

135

나는 팬입니다

chunk_size= 4
for i in range(0, len(ints), chunk_size):
    chunk = ints[i:i+chunk_size]
    # process chunk of size <= chunk_size

len (ints)가 chunkSize의 배수가 아닌 경우 어떻게 작동합니까?
PlsWork

3
@AnnaVopureta chunk는 마지막 배치 요소에 대해 1, 2 또는 3 개의 요소를 갖습니다. 슬라이스 인덱스가 범위를 벗어날 수있는 이유에 대해서는이 질문을 참조하십시오 .
Boris

22
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

2
제안 된 모든 솔루션 중에서 가장 "피 토닉"과 같은 이음새 생성기를 사용하는 경우 +1
Sergey Golovchenko

7
너무 쉬운 일이 너무 길고 어색합니다. 나는 S. Lott의 버전을 선호합니다
zenazn

4
@zenazn :이 발전기 인스턴스에서 작동, 슬라이스는하지 않습니다
야누스 Troelsen

생성기 및 기타 슬라이스 할 수없는 반복자와 올바르게 작동하는 것 외에도, 최종 청크가보다 작 으면 첫 번째 솔루션은 "필러"값을 필요로하지 않기 때문에 size때로는 바람직합니다.
dano

1
또한 발전기의 경우 +1입니다. 다른 솔루션에는 len전화가 필요 하므로 다른 생성기에서는 작동하지 않습니다.
Cuadue


11

이 문제에 대한 이상적인 솔루션은 시퀀스뿐만 아니라 반복자와도 작동합니다. 또한 빠릅니다.

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)

%timeit내 Mac Book Air에서 ipython을 사용하면 루프 당 47.5 us를 얻습니다.

그러나 결과가 짝수 그룹으로 채워지기 때문에 이것은 실제로 작동하지 않습니다. 패딩이없는 솔루션은 약간 더 복잡합니다. 가장 순진한 해결책은 다음과 같습니다.

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 us

내가 만들 수있는 가장 좋은 해결책 islice은 내부 루프에 대한 용도 입니다.

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

동일한 데이터 세트로 루프 당 305 us를 얻습니다.

그보다 더 빠른 솔루션을 얻을 수없는 경우 다음과 같은 솔루션에 중요한 경고를 제공합니다. 입력 데이터에 인스턴스가 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 us


레시피 # 3의 런타임을 C 레이어로 이동하여 ~ 10-15 %까지 줄일 수 있습니다 ( itertools수입 생략 ; mapPy3 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필러 값을 잘못 입력 할 수 없습니다 .
ShadowRanger

BTW, # 4에서 큰 엄지 손가락. 지금까지 게시 된 것보다 더 나은 답변 (성능 측면)으로 # 3에 대한 최적화를 게시하려고했지만 신뢰할 수 있고 탄력적 인 # 4가 최적화 된 # 3보다 두 배 빠르게 실행됩니다. 파이썬 레벨 루프 (및 이론적 인 알고리즘 차이 AFAICT)가있는 솔루션이 이길 것으로 기대하지 않았습니다. 나는 islice객체 를 생성 / 인용하는 비용으로 인해 # 3이 손실된다고 가정 합니다 ( n예 : 그룹 수가 적지 만 # 3 이 적 으면 흔하지 만 드문 경우에 최적화됩니다). 극단.
ShadowRanger

# 4의 경우 조건부의 첫 번째 분기는 마지막 반복 (최종 튜플)에서만 수행됩니다. 최종 튜플을 다시 재구성하는 대신 상단에서 원래 iterable 길이의 모듈로를 캐시하고이를 사용 izip_longest하여 최종 튜플 에서 원하지 않는 패딩을 잘라냅니다 yield i[:modulo]. 또한 args변수의 경우 목록 대신 튜플하십시오 args = (iter(iterable),) * n. 몇 번 더 클럭 사이클을 줄입니다. 마지막으로 fillvalue를 무시하고 가정 None하면 if None in i더 많은 클럭 사이클에 대해 조건이 될 수 있습니다 .
Kumba

1
@Kumba : 첫 번째 제안은 입력 길이를 알고 있다고 가정합니다. 알려진 길이의 컬렉션이 아닌 반복자 / 생성기 인 경우 캐시 할 것이 없습니다. 어쨌든 그러한 최적화를 사용해야 할 실제적인 이유는 없습니다. 일반적이지 않은 경우 (마지막 yield)를 최적화하는 반면 일반적인 경우에는 영향을 미치지 않습니다.
ShadowRanger

10

세트 및 생성기와 함께 작동하는 솔루션이 필요했습니다. 나는 매우 짧고 예쁜 것을 생각 해낼 수 없었지만 적어도 꽤 읽을 수 있습니다.

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]]

세트:

>>> 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]]

8

다른 제안과 비슷하지만 정확히 동일하지는 않지만 간단하고 읽기 쉽기 때문에 이런 식으로 수행하는 것이 좋습니다.

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_longestfrom을 사용하십시오 itertools.


로 개선 될 수있다zip(*([it]*4))
장 - 프랑수아 파브르

@ Jean-François Fabre : 가독성 관점에서 나는 그것을 개선으로 보지 않습니다. 그리고 그것은 또한 조금 느립니다. 당신이 골프를하고 있다면 그것은 개선입니다.
kriss

아니 골프는 아니지만 10 개의 논쟁이 있다면 어떨까요? 나는 공식 페이지에서 그 구조를 읽었지만 물론 지금 그것을 찾을 수없는 것 같습니다 :)
Jean-François Fabre

@ Jean-François Fabre : 10 개의 인수 또는 가변 개수의 인수가있는 경우 옵션이지만 zip (* (it,) * 10)
kriss

권리! 그건 내가 무엇을 읽고. 내가 만든 목록이 아닙니다 :)
Jean-François Fabre

8

외부 패키지를 사용하는 것이 마음에 들지 않으면 1iteration_utilities.grouper 에서 사용할 수 있습니다 . 시퀀스뿐만 아니라 모든 iterable을 지원합니다.iteration_utilties

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)

벤치 마크

또한 언급 된 몇 가지 접근 방식의 런타임을 비교하기로 결정했습니다. 다양한 크기의 목록을 기반으로 "10"요소 그룹으로 그룹화 된 로그-로그 플롯입니다. 질적 결과의 경우 : 낮을수록 더 빠릅니다.

여기에 이미지 설명을 입력하십시오

적어도이 벤치 마크에서 iteration_utilities.grouper성능이 가장 좋습니다. Craz 의 접근 방식 .

벤치 마크는 1 로 만들어졌습니다 . 이 벤치 마크를 실행하는 데 사용 된 코드는 다음과 같습니다.simple_benchmark

import iteration_utilities
import itertools
from itertools import zip_longest

def consume_all(it):
    return iteration_utilities.consume(it, None)

import simple_benchmark
b = simple_benchmark.BenchmarkBuilder()

@b.add_function()
def grouper(l, n):
    return consume_all(iteration_utilities.grouper(l, n))

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

@b.add_function()
def Craz(iterable, n, fillvalue=None):
    return consume_all(Craz_inner(iterable, n, fillvalue))

def nosklo_inner(seq, size):
    return (seq[pos:pos + size] for pos in range(0, len(seq), size))

@b.add_function()
def nosklo(seq, size):
    return consume_all(nosklo_inner(seq, size))

def SLott_inner(ints, chunk_size):
    for i in range(0, len(ints), chunk_size):
        yield ints[i:i+chunk_size]

@b.add_function()
def SLott(ints, chunk_size):
    return consume_all(SLott_inner(ints, chunk_size))

def MarkusJarderot1_inner(iterable,size):
    it = iter(iterable)
    chunk = tuple(itertools.islice(it,size))
    while chunk:
        yield chunk
        chunk = tuple(itertools.islice(it,size))

@b.add_function()
def MarkusJarderot1(iterable,size):
    return consume_all(MarkusJarderot1_inner(iterable,size))

def MarkusJarderot2_inner(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))

@b.add_function()
def MarkusJarderot2(iterable,size):
    return consume_all(MarkusJarderot2_inner(iterable,size))

@b.add_arguments()
def argument_provider():
    for exp in range(2, 20):
        size = 2**exp
        yield size, simple_benchmark.MultiArgument([[0] * size, 10])

r = b.run()

1 개 면책 조항 : 나는 라이브러리의 저자 해요 iteration_utilitiessimple_benchmark.


7

아무도 언급하지 않았으므로 여기에 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)

5

zip () 대신 map ()을 사용하면 JF Sebastian의 답변에서 패딩 문제가 해결됩니다.

>>> 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')]

2
이것은 itertools.izip_longest(Py2) / itertools.zip_longest(Py3) 으로 더 잘 처리됩니다 . 이 사용 map은 이중으로 더 이상 사용되지 않으며 Py3에서는 사용할 수 없습니다 ( None매퍼 기능으로 전달할 수 없으며 가장 긴 iterable이 소진되면 가장 길지 않고 패딩되지 않습니다).
ShadowRanger

4

또 다른 접근법은 다음과 같은 두 가지 인수 형식을 사용하는 것입니다 iter.

from itertools import islice

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

패딩을 사용하도록 쉽게 조정할 수 있습니다 ( Markus Jarderot 의 답변 과 유사 ).

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)

1
패딩을 생략 할 수있는 옵션이 있으므로 선호됩니다!
n611x007

3

목록이 큰 경우이를 수행하는 가장 좋은 방법은 생성기를 사용하는 것입니다.

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,)

(저는 MizardX의 itertools 제안이 이것과 기능적으로 동일하다고 생각합니다.)
Robert Rossney

1
(실제로, 반성에, 나는 그것을하지 않습니다. itertools.islice는 반복자를 반환하지만, 기존 반복기를 사용하지 않습니다.)
Robert Rossney

그것은 좋은 간단하지만도 변환없이 어떤 이유에 4-7 배 느리게 허용 식용 방법보다 튜플 iterable = range(100000000)& chunksize10000까지
Valentas

그러나 마지막 항목을 확인할 때 허용되는 항목이 매우 느릴 수 있으므로 일반적 으로이 방법을 권장합니다. docs.python.org/3/library/itertools.html#itertools.zip_longest
Valentas

3

작은 기능과 사물을 사용하는 것이 실제로 나에게 호소력이 없습니다. 슬라이스 만 사용하는 것을 선호합니다.

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:
    ...

알려지지 않은 무한 스트림에는 좋지만 좋지 않습니다 len. itertools.repeat또는 로 테스트를 수행 할 수 있습니다 itertools.cycle.
n611x007

1
또한 다음 요소와 여분의 메모리 만 신경 쓰는 생성기 표현식 대신 [...for...] 목록 이해 를 사용하여 물리적 으로 목록을 작성 하기 때문에 메모리를 (...for...)
소모

2

목록으로의 모든 변환을 피하려면 다음을 수행하십시오 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 실제로 생각할 때까지 각 값의 해상도가 지연됩니다. 슬프게도 (현재) 사용 가능한 답변 중 어느 것도이 변형을 제공하지 않는 것 같습니다.

분명히 각 항목을 처리 해야하는 경우 g 위에 for 루프를 중첩하십시오.

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

이것에 대한 나의 관심은 gmail API에 최대 1000 개의 배치 변경 사항을 제출하기 위해 생성기를 소비해야한다는 것입니다.

    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)

청크 목록이 오름차순 정수가 아닌 경우 어떻게됩니까?
PaulMcG

@PaulMcGuire는 groupby를 참조하십시오 ; 순서를 설명하는 함수가 주어지면 iterable의 요소는 무엇이든 될 수 있습니다.
John Mee

1
예, 저는 그룹별로 잘 알고 있습니다. 그러나 메시지가 "ABCDEFG"문자 groupby(messages, lambda x: x/3)인 경우 3 문자 그룹화가 아니라 TypeError (문자열을 정수로 나누기위한 시도)를 제공합니다. 이제 groupby(enumerate(messages), lambda x: x[0]/3)당신이 한 것이 있다면 뭔가있을 것입니다. 하지만 당신은 당신의 게시물에 그런 말을하지 않았다.
PaulMcG

2

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
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()

2

내가 뭔가를 그리워하지 않는 한, 생성기 표현식이있는 다음과 같은 간단한 해결책은 언급되지 않았습니다. 그것은 가정 그 크기와 알려져있다 덩어리의 수를 모두 (보통의 경우), 그리고 패딩이 필요하지 않다는 :

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))

1

두 번째 방법에서는 다음을 수행하여 다음 4 그룹으로 진행합니다.

ints = ints[4:]

그러나 성능 측정을 수행하지 않았으므로 어느 것이 더 효율적인지 알 수 없습니다.

그렇게 말하면서, 나는 보통 첫 번째 방법을 선택할 것입니다. 예쁘지는 않지만 종종 외부 세계와의 인터페이스의 결과입니다.


1

또 다른 대답은 다음과 같은 장점이 있습니다.

1) 쉽게 이해할 수 있음
2) 시퀀스뿐만 아니라 반복 가능한 작업 (위의 답변 중 일부는 파일 핸들을 질식시킵니다)
3) 청크를 한 번에 메모리에로드
하지 않습니다. 메모리에서 동일한 반복자
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 

업데이트 :
내부 및 외부 루프가 동일한 반복기에서 값을 가져오고 있다는 사실로 인한 몇 가지 단점 :
1) 외부 루프에서 예상대로 작동하지 않습니다-청크를 건너 뛰지 않고 다음 항목으로 계속 진행합니다. . 그러나 외부 루프에는 테스트 할 것이 없으므로 문제가되지 않습니다.
2) 내부 루프에서 예상대로 브레이크가 작동하지 않습니다. 반복기의 다음 항목으로 제어가 내부 루프에서 다시 감 깁니다. 전체 청크를 건너 뛰려면 내부 반복자 (위의 ii)를 튜플에 싸 for c in tuple(ii)거나 플래그를 설정하고 반복자를 소진합니다.


1
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 패딩을 생략합니다. 당신과 bcoughlan '들 과 매우 유사
n611x007

1

funcy 라이브러리 에서 파티션 또는 청크 기능을 사용할 수 있습니다 .

from funcy import partition

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

이 함수에는 반복자 버전 ipartition및 이 있으며이 ichunks경우 더 효율적입니다.

당신은 또한 그들의 구현을 엿볼 수 있습니다 .


1

J.F. Sebastian 여기에 제공된 솔루션 정보 :

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

그것은 영리하지만 한 가지 단점이 있습니다-항상 튜플을 반환하십시오. 대신 문자열을 얻는 방법?
물론 쓸 수 있습니다''.join(chunker(...)) 있지만 임시 튜플은 어쨌든 구성됩니다.

다음 zip과 같이 own을 작성하여 임시 튜플을 제거 할 수 있습니다 .

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'

2
답변을 바꾸라는 비판이 아니라 의견입니다. 코드는 책임입니다. 코드를 많이 작성할수록 버그를 숨길 수있는 공간이 더 많아집니다. 이러한 관점 zip에서 기존의 것을 사용하는 대신 다시 쓰는 것이 최선의 아이디어가 아닌 것 같습니다.
Alfe

1

나는이 접근법을 좋아한다. 단순하고 마술 적이 지 않고 반복 가능한 모든 유형을 지원하며 가져 오기가 필요하지 않습니다.

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

나는 덩어리가 패딩되기를 원하지 않기 때문에 요구 사항이 필수적입니다. 반복 가능한 작업을 수행하는 능력도 요구 사항이라는 것을 알았습니다. 그 점을 감안할 때 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

다음은 생성기를 지원하는 가져 오기가없는 청커입니다.

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

Python 3.8에서는 walrus 연산자 및을 사용할 수 있습니다 itertools.islice.

from itertools import islice

list_ = [i for i in range(10, 100)]

def chunker(it, size):
    iterator = iter(it)
    while chunk := list(islice(iterator, size)):
        print(chunk)
In [2]: chunker(list_, 10)                                                         
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
[20, 21, 22, 23, 24, 25, 26, 27, 28, 29]
[30, 31, 32, 33, 34, 35, 36, 37, 38, 39]
[40, 41, 42, 43, 44, 45, 46, 47, 48, 49]
[50, 51, 52, 53, 54, 55, 56, 57, 58, 59]
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69]
[70, 71, 72, 73, 74, 75, 76, 77, 78, 79]
[80, 81, 82, 83, 84, 85, 86, 87, 88, 89]
[90, 91, 92, 93, 94, 95, 96, 97, 98, 99]

0

이것을 할 수있는 좋은 방법이없는 것 같습니다. 다음 은 여러 가지 방법이있는 페이지입니다.

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

목록의 크기가 같은 경우을 사용하여 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

하나 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 ...
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.