私は、の相互作用にもう少し光当てたいと思い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__
(またはnext
Python 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__
(またはnext
Python 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__
オブジェクトを反復可能にするのにも十分です