私は、の相互作用にもう少し光当てたいと思いiter、__iter__そして__getitem__、どのようなカーテンの後ろに起こります。その知識で武装すると、あなたができる最善のことはなぜであるかを理解することができます
try:
iter(maybe_iterable)
print('iteration will probably work')
except TypeError:
print('not iterable')
最初に事実をリストしてからfor、Pythonでループを使用したときに何が起こるかを簡単に思い出させてから、事実を説明するためのディスカッションを行います。
事実
あなたは、任意のオブジェクトからイテレータを取得することができますo呼び出すことによってiter(o)、次の条件の少なくとも一方が成立する場合
)oがある__iter__イテレータオブジェクトを返すメソッドを。イテレータとは__iter__、__next__(Python 2:)nextメソッドを持つ任意のオブジェクトです。
B)o有する__getitem__方法。
Iterableまたはのインスタンスを確認するかSequence、属性を確認する__iter__だけでは不十分です。
オブジェクトの場合o器具だけ__getitem__ではなく__iter__、iter(o)試行からアイテムをフェッチすること反復子建設するoインデックス0から始まる整数インデックスでの反復子は、任意のキャッチするIndexError上昇である(ただし、他のエラー)を、次に上げるStopIteration自体。
最も一般的な意味では、によって返されたイテレータiterが正しいかどうかを確認する方法は、それを試す以外にありません。
オブジェクトがをo実装している__iter__場合、iter関数はによって返されるオブジェクト__iter__がイテレータであることを確認します。オブジェクトがのみを実装するかどうかの健全性チェックはありません__getitem__。
__iter__勝ちます。オブジェクトがとのo両方__iter__を実装している場合は__getitem__、iter(o)が呼び出されます__iter__。
独自のオブジェクトを反復可能にしたい場合は、常に__iter__メソッドを実装してください。
for ループ
従うためにはfor、Pythonでループを使用するときに何が起こるかを理解する必要があります。既にご存知の場合は、次のセクションに進んでください。
for item in oいくつかの反復可能なオブジェクトに使用するとo、Pythonはiter(o)反復子オブジェクトを呼び出し、戻り値として期待します。イテレータは、__next__(またはnextPython 2で)メソッドとメソッドを実装するオブジェクト__iter__です。
慣例により__iter__、イテレータのメソッドはオブジェクト自体(つまりreturn self)を返す必要があります。次にPythonは、が発生するnextまでイテレータを呼び出しますStopIteration。これはすべて暗黙的に行われますが、次のデモではそれを表示します。
import random
class DemoIterable(object):
def __iter__(self):
print('__iter__ called')
return DemoIterator()
class DemoIterator(object):
def __iter__(self):
return self
def __next__(self):
print('__next__ called')
r = random.randint(1, 10)
if r == 5:
print('raising StopIteration')
raise StopIteration
return r
の反復DemoIterable:
>>> di = DemoIterable()
>>> for x in di:
... print(x)
...
__iter__ called
__next__ called
9
__next__ called
8
__next__ called
10
__next__ called
3
__next__ called
10
__next__ called
raising StopIteration
ディスカッションとイラスト
ポイント1および2:反復子と信頼性の低いチェックの取得
次のクラスについて考えてみます。
class BasicIterable(object):
def __getitem__(self, item):
if item == 3:
raise IndexError
return item
のiterインスタンスで呼び出すと、が実装されてBasicIterableいるため、問題なくイテレータが返さBasicIterableれ__getitem__ます。
>>> b = BasicIterable()
>>> iter(b)
<iterator object at 0x7f1ab216e320>
ただし、bは__iter__属性を持たず、Iterableまたはのインスタンスとは見なされないことに注意することが重要ですSequence。
>>> from collections import Iterable, Sequence
>>> hasattr(b, '__iter__')
False
>>> isinstance(b, Iterable)
False
>>> isinstance(b, Sequence)
False
Luciano RamalhoによるFluent Pythonが、オブジェクトが反復可能かどうかを確認する最も正確な方法として、iter可能性TypeErrorを呼び出して処理することを推奨するのはこのためです。本から直接引用:
Python 3.4以降、オブジェクトxが反復可能かどうかを確認する最も正確な方法は、反復可能でない場合は例外を呼び出しiter(x)て処理するTypeErrorことです。ABCはそうではないが、従来の方法も考慮しているisinstance(x, abc.Iterable)ため、これはを使用するよりも正確です。iter(x)__getitem__Iterable
ポイント3日:のみ提供するオブジェクトの繰り返し処理__getitem__ではなく、__iter__
BasicIterable期待どおりに作品のインスタンスを反復処理します。Pythonは、インデックスIndexErrorが発生するまで、ゼロから開始してインデックスで項目をフェッチしようとする反復子を構築します。デモオブジェクトの__getitem__メソッドは、によって返されたイテレータによってitem引数として渡されたを返すだけです。__getitem__(self, item)iter
>>> b = BasicIterable()
>>> it = iter(b)
>>> next(it)
0
>>> next(it)
1
>>> next(it)
2
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
イテレータはStopIteration次のアイテムを返すことができない場合に発生し、IndexError発生したitem == 3は内部的に処理されることに注意してください。これが、ループBasicIterableでのforループが期待どおりに機能する理由です。
>>> for x in b:
... print(x)
...
0
1
2
が返すイテレータiterがインデックスでアイテムにアクセスする方法の概念を理解するための別の例を次に示します。WrappedDictはから継承しません。dictつまり、インスタンスには__iter__メソッドがありません。
class WrappedDict(object): # note: no inheritance from dict!
def __init__(self, dic):
self._dict = dic
def __getitem__(self, item):
try:
return self._dict[item] # delegate to dict.__getitem__
except KeyError:
raise IndexError
への呼び出し__getitem__はdict.__getitem__、角括弧表記が単に省略形である場合に委譲されることに注意してください。
>>> w = WrappedDict({-1: 'not printed',
... 0: 'hi', 1: 'StackOverflow', 2: '!',
... 4: 'not printed',
... 'x': 'not printed'})
>>> for x in w:
... print(x)
...
hi
StackOverflow
!
ポイント4および5 iterでは、次の呼び出し時にイテレータをチェックします__iter__。
ときにiter(o)オブジェクトに対して呼び出されo、iter戻り値があることを確認します__iter__方法が存在する場合、イテレータです。つまり、返されたオブジェクトは__next__(またはnextPython 2では)およびを実装する必要があり__iter__ます。iterオブジェクト__getitem__のアイテムに整数インデックスでアクセスできるかどうかをチェックする方法がないため、だけを提供するオブジェクトの健全性チェックを実行できません。
class FailIterIterable(object):
def __iter__(self):
return object() # not an iterator
class FailGetitemIterable(object):
def __getitem__(self, item):
raise Exception
FailIterIterableインスタンスからのイテレータの構築はすぐに失敗しますが、イテレータの構築はFailGetItemIterable成功しますが、への最初の呼び出しで例外がスローされ__next__ます。
>>> fii = FailIterIterable()
>>> iter(fii)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: iter() returned non-iterator of type 'object'
>>>
>>> fgi = FailGetitemIterable()
>>> it = iter(fgi)
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/path/iterdemo.py", line 42, in __getitem__
raise Exception
Exception
ポイント6:__iter__勝利
これは簡単です。オブジェクトが__iter__andを実装している場合は__getitem__、iterを呼び出します__iter__。次のクラスを検討してください
class IterWinsDemo(object):
def __iter__(self):
return iter(['__iter__', 'wins'])
def __getitem__(self, item):
return ['__getitem__', 'wins'][item]
インスタンスをループするときの出力:
>>> iwd = IterWinsDemo()
>>> for x in iwd:
... print(x)
...
__iter__
wins
ポイント7:イテラブルクラスは実装する必要があります __iter__
ほとんどの組み込みシーケンスがメソッドをlist実装するのに十分である理由を自問するかもしれません。__iter____getitem__
class WrappedList(object): # note: no inheritance from list!
def __init__(self, lst):
self._list = lst
def __getitem__(self, item):
return self._list[item]
結局のところ、(角括弧表記を使用して)__getitem__への呼び出しを委譲する上記のクラスのインスタンスに対する反復は、list.__getitem__正常に機能します。
>>> wl = WrappedList(['A', 'B', 'C'])
>>> for x in wl:
... print(x)
...
A
B
C
カスタム反復可能オブジェクトが実装__iter__する必要がある理由は次のとおりです。
- を実装すると
__iter__、インスタンスは反復可能と見なされ、isinstance(o, collections.abc.Iterable)を返しTrueます。
- によって返されたオブジェクトが
__iter__イテレータでない場合、はiterすぐに失敗し、を発生させTypeErrorます。
- の特別な処理は
__getitem__、下位互換性のために存在します。Fluent Pythonからの引用:
これが、Pythonシーケンスが反復可能である理由__getitem__です。それらはすべてを実装します。実際、標準シーケンスもを実装し__iter__ます。これは__getitem__、下位互換性のためにの特別な処理が存在し、将来はなくなる可能性があるためです(ただし、私がこれを書いているため、非推奨ではありません)。
__getitem__オブジェクトを反復可能にするのにも十分です