nozayasu-memo

プログラムのメモ

more_itertools chunkedの処理を読む

実際のコードは以下

def chunked(iterable, n):
    """Break *iterable* into lists of length *n*:
        >>> list(chunked([1, 2, 3, 4, 5, 6], 3))
        [[1, 2, 3], [4, 5, 6]]
    If the length of *iterable* is not evenly divisible by *n*, the last
    returned list will be shorter:
        >>> list(chunked([1, 2, 3, 4, 5, 6, 7, 8], 3))
        [[1, 2, 3], [4, 5, 6], [7, 8]]
    To use a fill-in value instead, see the :func:`grouper` recipe.
    :func:`chunked` is useful for splitting up a computation on a large number
    of keys into batches, to be pickled and sent off to worker processes. One
    example is operations on rows in MySQL, which does not implement
    server-side cursors properly and would otherwise load the entire dataset
    into RAM on the client.
    """
    return iter(partial(take, n, iter(iterable)), [])

see also
https://github.com/erikrose/more-itertools/blob/17bd5449e9872e27053e408af5832715821add36/more_itertools/more.py#L69-L90

要素を分解する

実際の処理はここだけ

return iter(partial(take, n, iter(iterable)), [])

iter, partial, takeという処理を組み合わせて実現してる、それぞれみてみる。

iter

https://docs.python.org/3/library/functions.html#iter

イテレータオブジェクトを返してくれる 引数の内容によって振る舞いがかわってくる

>>> iter(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable
>>> iter("こんにちは")
<str_iterator object at 0x10dbf7c18>
>>> iter("こんにちは", "")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: iter(v, w): v must be callable
>>> iter(int, 0)
<callable_iterator object at 0x10dbf7cc0>

1つだけ引数を渡す場合、イテレートできないようなオブジェクトだとTypeErrorになる
2つ引数を渡す場合、callできないようなオブジェクトだとTypeErrorになる、callした結果が2つ目の引数と一致するまでcallし続ける

partial

https://docs.python.org/3/library/functools.html#functools.partial

任意のfunctionにおいて、特定引数のdefault値が設定された新たなfunctionを生成してくれる
次みたいなのができるになる

>>> from functools import partial
>>> def say(word):
...     print(word)
... 
>>> say("こんにちは")
こんにちは
>>> new_say = partial(say, word="こんばんは")
>>> new_say()
こんばんは
>>> new_say(word="おやすみ")
おやすみ

take

https://github.com/erikrose/more-itertools/blob/17bd5449e9872e27053e408af5832715821add36/more_itertools/recipes.py#L80-L92

イテレータオブジェクトの頭からn回処理した結果をリストとして返してくれる

def take(n, iterable):
    """Return first *n* items of the iterable as a list.
        >>> take(3, range(10))
        [0, 1, 2]
        >>> take(5, range(3))
        [0, 1, 2]
    Effectively a short replacement for ``next`` based iterator consumption
    when you want more than one item, but less than the whole iterator.
    """
    return list(islice(iterable, n))

ここでつかってるisliceはイテレータオブジェクトの頭からn回処理した結果を返す

islice: https://docs.python.org/3.6/library/itertools.html#itertools.islice

list()しない場合とでみるとわかりやすいかも

>>> from itertools import islice
>>> word = "こんにちは"
>>> islice(word, 3)
<itertools.islice object at 0x10dbf8728>
>>> list(islice(word, 3))
['こ', 'ん', 'に']
>>> 

どんなことやらせてるのか

実際の処理の部分を再掲

def chunked(iterable, n):
    return iter(partial(take, n, iter(iterable)), [])
  • iter(iterable)イテレータオブジェクトを生成してる
  • partial(take, n, iter(iterable))でtakeに対してnとiter(iterable)が与えられた状態のtake functionを生成してる
  • iter(partial(take, n, iter(iterable)), [])で新たに生成したtake functionの返りが[]となるまで繰り返すようなイテレータオブジェクトを生成している

という感じのことだということがわかった。