いろいろ備忘録日記

主に .NET とか Java とか Python絡みのメモを公開しています。

Pythonメモ-33 (iter関数) (iter(callable, sentinel), iter関数の引数2つ版)

概要

たまーにしか使わないけど、忘れるのでメモメモ。

組み込み関数 iter() には、引数が一つ版と引数2つ版があります。

それぞれ、書式は以下となります。

iter(iterable)
iter(callable, sentinel)

なんか、二つ目の引数がちょっと変・・・w

iter()さんは、引数が二つの場合は callable を受け取ります。

で、sentinel の値が来るまで、ずっと callable を呼び続けるという動作をします。

sentinel ってのは、日本語でいうと「番兵」です。プログラミングでよく出てきますね。

なので、何かの値が現れるまで、ずっと処理続けるってときに

results = set()
for x in y:
    if x == sentinel:
        break
    results.add(x)

って書くところを

for x in iter(callable, sentinel):
    results.add(x)

または

results = {x for x in iter(callable, sentinel)}

って書けてちょっとスマートな感じになります。

で、よくやってしまう失敗として list を渡してしまう。

l = list(range(10))
r = [x for x in iter(l, 5)]

実行すると

TypeError: iter(v, w): v must be callable

ってなります。list は callable では無いからです。

で、よく使われるのがやっぱりファイル処理とか、バイトデータを検索したりするときとか、ランダムなデータが来るので特定の値が来るまで続けるとか。

何らかの値が来たら終わりってするときに

with open('xxxxx', mode='r', encoding='utf-8') as f:
    for line in iter(f.readline, 'hoge'):
        print(line)

ってするといい感じ。(PythonのAPIリファレンスの方は、空文字で見てますが)

でも、問題点は sentinel の値が固定で判定されているという点です。

実際につくる時に、ここが固定じゃない場合があります。

何らかの値の内のどれかが来たら終わりにするとか。

その場合は、sentinel を表現するものを作ってやるとうまくいきます。

内部では、== で一致するかを見ているので、__eq__(self, other) を定義してやればいいはず。

(お作法として、__ne__(self, other) も定義しておくほうが良いですね。)

サンプル

以下、とりあえず忘れないためのクソサンプル。

# coding: utf-8
"""
組み込み関数 iter(callable, sentinel) についてのサンプルです。
"""

import os

from trypython.common.commoncls import SampleBase
from trypython.common.commonfunc import pr


class MySentinel:
    def __eq__(self, o: object) -> bool:
        return 'sentinel' == str(o).replace('\n', '')


class Sample(SampleBase):
    def __init__(self) -> None:
        super().__init__()
        self._file = '/tmp/test.txt'

    def exec(self):
        ####################################################
        # iter(callable, sentinel)
        #
        # 組み込み関数の iter は、引数が一つの場合と
        # 二つの場合で挙動が異なる。
        #
        # 引数が一つの場合、要求される引数は iterable だが
        # 引数が二つの場合、要求される引数は
        #    callable と sentinel
        # となる。
        #
        # sentinel は、日本語で言うと「番兵」の事。
        # iter(callable, sentinel) は、毎回 callable を
        # 呼び出して、その値が sentinel と等しいかを判定する。
        #
        # 等しい場合は、StopIteration が発生して
        # イテレーションが終わる。
        #
        # なので、何かの値が出てくるまで継続するという
        # 処理と記述する時に使える。
        ####################################################
        try:
            self._write_dummy_file()

            with open(self._file, mode='r', encoding='utf-8') as f:
                sentinel = MySentinel()
                it = iter(f.readline, sentinel)

                for i, line in enumerate(it):
                    pr(f'line-{i:02}', line)
        finally:
            self._delete_dummy_file()

    def _write_dummy_file(self):
        with open(self._file, mode='w', encoding='utf-8', newline='') as f:
            print('helloworld', file=f)
            print('sentinel', file=f)
            print('helloworld', file=f)

    def _delete_dummy_file(self):
        if os.path.exists(self._file):
            os.remove(self._file)


def go():
    obj = Sample()
    obj.exec()


if __name__ == '__main__':
    go()

実行すると以下となります。

line-00='helloworld\n'

参考情報

2. 組み込み関数 — Python 3.6.3 ドキュメント

3. データモデル — Python 3.6.3 ドキュメント

stackoverflow.com


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。