안녕하세요. 장고에서 bulk create 함수를 쓰다가 알게된 itertools의 islice 함수에 대해 간단하게 소개해 보겠습니다.

 


 먼저, islice는 iterator를 리턴합니다. 이 iterator의 next() 메소드가 iterable로부터 선택된 값들을 리턴합니다. 이게 무슨 이야기인지 밑에서 후술하도록 하겠습니다. iterable, start, stop, step 요래 받으면, start로부터 step만큼 증가하는 iterator를 리턴하게 됩니다. step이 0보다 큰 정수라면 어디까지 증가할까요? stop 미만까지 증가합니다. 예제를 몇 개 보면서 이해해 보겠습니다.

 

 

 gen에는 [3, 5, 7, ... , 23]이 저장되어 있습니다. islice의 iterable한 것은 list인 gen이 넘어갔습니다. start는 0, end는 10, step은 2입니다. 이제 어떻게 된 일인지 차근차근 설명해 보겠습니다.

 

 먼저 start가 0이였으니, gen의 1번째 위치를 iterator가 가르켰을 겁니다. 다음에, step이 2입니다. 이는 다음 값을 뽑기 위해서 얼만큼이나 뛸 것인지를 정의합니다. 2이므로, 2칸만큼 뛰게 됩니다. next()가 이 때 호출됩니다.

 

 2칸만큼 건너 뛰었으니, 이제 7이 리턴됩니다. 다음 2칸, 2칸, 2칸, 2칸을 건너 뛰면 11, 15, 19가 나오게 됩니다. 19가 나왔을 때, 건너뛴 총 step은 8이 됩니다.

 

 

 23으로 갔을 때 이미 10을 건너 뛰었습니다. 처음 위치로부터요. 그런데, 이미 총 10번 이상 뛴 상태이므로, 23은 value값에 포함되지 않습니다. break가 되어 버립니다.

 

 따라서, [3, 7, 11, 15, 19]가 리턴됩니다. end는 몇 개의 원소를 보면 break를 걸 지 설정하는 것이라고 생각하면 편하겠습니다. 예를 들어 end가 10이면, 처음 위치로부터 10개 이상 건너뛰면 break를 겁니다.

 

 

 이 코드는 어떻게 동작할까요? stop하고 step이 예제 1과 같습니다. 단지 다른 것은 start입니다. start가 양수이면 처음에 몇 개의 수를 뛸지를 결정합니다. 다시 gen으로 돌아와 봅시다.

 

 

 gen의 처음 위치는 3입니다. 여기서 2개의 원소를 건너뛰어 봅시다.

 

 3, 5를 건너 뛰면, 7이 나옵니다. 따라서 7부터 원소를 뽑기 시작합니다. start가 k이면 처음 k개를 뛰고 원소를 뽑게 됩니다.

 

 

 실행 결과는 [7, 11, 15, 19]가 나옵니다. 여기까지만 보면 별 게 아닐 거 같습니다만, generator와 쓰면 생각보다 강력합니다.

 


 장고 문서에서 bulk_create 메서드를 설명할 때, islice랑 같이 쓰는 예제가 하나 있습니다. batch로 밀어넣을 때 상당히 유용하게 쓰일 수 있다는 것입니다.

 

 iterable과 stop만 넣으면, 1번째 원소부터 stop번째까지의 원소를 뽑습니다. 이것을 next()를 써서 하는 것입니다. 그런데, 제너레이터는 next를 호출할 때 마다, yield로 실행 위치를 저장해 놓습니다.

 

 8번째 줄에 의해 next가 2번 호출됩니다. 처음에 yield 0에 의해, 0이 리턴됩니다. 다음 yield 1에 의해, 1이 리턴됩니다. 이 결과를 9번째 줄에 의해 list로 변환해서 출력하게 됩니다. 다음에 10번째 줄에서 또 islice를 호출하게 됩니다. 그런데, 이 시점에 이미 yield 1을 한 context를 기억하고 있어요. 그렇기 때문에, 다음 yield 2, yield 3까지 실행을 하게 됩니다.

 

 이제 print에 의해 [2, 3]이 출력되게 됩니다.

 

 실행 결과는 위와 같이 나옵니다. 여기까지 실행 과정을 대강 이해하셨다면 아래 코드도 이해하실 수 있습니다.

 

 

2번째 줄은 generator gen을 생성합니다. 1부터 17까지 생성합니다. 6번째 줄을 보면 islice(gen, 4)로 되어 있습니다. 이는, 4개의 원소를 뽑는 것을 의미합니다. 그러면, 1, 2, 3, 4를 뽑은 다음에 출력을 하게 될 겁니다.

 

 중요한 것은 4개의 원소를 리턴했다는 context 정보가 저장된다는 것입니다. 고로, 다음 islice의 next가 수행될 때, 다음 yield 4 이후부터 수행되게 됩니다. 이런 식으로 원소를 4개씩 소모하게 되는 것입니다. 실행 결과는 아래와 같습니다.

 

 

 1부터 17까지 뽑는데, 매 루프마다 4개씩 소모함을 알 수 있어요. batch 연산을 할 때, 상당히 많이 보이는 패턴이니 알아두면 좋을 수도 있겠습니다.