Puzzle 2: __iter__
VS. __getitem__
イテラブルオブジェクトとは__iter__()
または__getitem__()
が定義されたクラスのことである。
次のプログラムでは半角英大文字をたどるイテレータを提供するイテラブルオブジェクトのクラスが定義されている。
以下を実行するとどのような出力が返されるか。
import string class UpperCase(object): def __init__(self, first=None): self.index = -1 if first is None else string.ascii_uppercase.index(first) -1 def __call__(self): self.index += 1 if self.index < len(string.ascii_uppercase): return self.__getitem__(self.index) raise StopIteration() def __getitem__(self, index): if 0 <= index < 26: return string.ascii_uppercase[index] raise IndexError() def __iter__(self): return (c for c in reversed(string.ascii_uppercase)) uc = UpperCase('J') print("".join(c for c in uc)) print("".join(c for c in iter(uc, 'N')))
Solution 2: __iter__
VS. __getitem__
出力結果は次のようになる。
ZYXWVUTSRQPONMLKJIHGFEDCBA JKLM
この動作を理解する鍵は組み込み函数iter()
である。
『Python文法詳解』の「7.3.1 イテラブルなオブジェクト」(P.258)には次のように説明してある。
iterable
が__iter__()
メソッドを実装している場合は、呼び出してイテレータを取得します。__iter__()
がなく、__getitem__()
メソッドを実装している場合は、__getitem__()
を呼び出して値を返すイテレータを作成します。
つまり、print("".join(c for c in uc))
は__iter__()
が実装してあったため、
__iter__()
が呼び出され、__getitem__()
は呼ばれなかったのである。
iter()
はイテラブルオブジェクトではなく、呼び出し可能オブジェクトと番兵を与えてイテレータオブジェクトを作る方法も存在する。
公式ドキュメントから引用する。
第二引数
sentinel
が与えられているなら、object
は呼び出し可能オブジェクトでなければなりません。 この場合に生成されるイテレータは、__next__()
を呼ぶ毎にobject
を引数無しで呼び出します。 返された値がsentinel
と等しければ、StopIteration
が送出され、そうでなければ、戻り値がそのまま返されます。
つまり、print("".join(c for c in iter(uc, 'N')))
とすると
uc()
が'N'が現れるかイテレータが尽きるまで繰り返し呼び出される。
また、__init__()
においてfirst
を渡すと指定した大文字からイテレートが開始するように細工してある。
よって、JKLM
と出力される。