回答:
Pythonのイテレータオブジェクトはイテレータプロトコルに準拠しています。つまり、基本的には、との2つのメソッドを提供__iter__()
し __next__()
ます。
__iter__
イテレータオブジェクトを返し、暗黙的にループの開始時に呼び出されます。
この__next__()
メソッドは次の値を返し、ループの増分ごとに暗黙的に呼び出されます。このメソッドは、返す値がなくなるとStopIteration例外を発生させます。これは、ループ構造によって暗黙的にキャプチャされ、反復を停止します。
カウンタの簡単な例を次に示します。
class Counter:
def __init__(self, low, high):
self.current = low - 1
self.high = high
def __iter__(self):
return self
def __next__(self): # Python 2: def next(self)
self.current += 1
if self.current < self.high:
return self.current
raise StopIteration
for c in Counter(3, 9):
print(c)
これは印刷されます:
3
4
5
6
7
8
これは、前の回答で説明したように、ジェネレーターを使用して書く方が簡単です。
def counter(low, high):
current = low
while current < high:
yield current
current += 1
for c in counter(3, 9):
print(c)
印刷出力は同じになります。内部的には、ジェネレーターオブジェクトはイテレータープロトコルをサポートし、クラスカウンターとほぼ同じような処理を行います。
David Mertzの記事「イテレーターとシンプルなジェネレーター」は、非常に優れた紹介です。
__next__
。counter
イテレータですが、シーケンスではありません。値は保存されません。たとえば、二重にネストされたforループでカウンターを使用しないでください。
__iter__
(に加えて__init__
)self.currentを割り当てます。それ以外の場合、オブジェクトは1回だけ反復できます。たとえば、と言った場合、一度ctr = Counters(3, 8)
しか使用できませんfor c in ctr
。
Counter
はイテレータであり、イテレータは一度だけ反復されることになっています。でリセットself.current
する__iter__
と、のネストされたループCounter
が完全に壊れ、イテレータの想定されたすべての動作(呼び出しはべきiter
等)に違反します。ctr
複数回反復できるようにしたい場合は、反復可能でない反復可能である必要があり、__iter__
呼び出されるたびに新しい反復を返します。混合して照合しようとすると(__iter__
呼び出されたときに暗黙的にリセットされるイテレータ)、プロトコルに違反します。
Counter
イテレータ以外の反復可能である場合は、__next__
/ の定義をnext
完全に削除し、おそらく__iter__
この回答の最後に説明されているジェネレータと同じ形式のジェネレータ関数として再定義します(範囲の代わりに)引数からに来る__iter__
彼らは、引数にはするだろう、__init__
上に保存self
してからアクセスself
で__iter__
)。
反復関数を作成するには4つの方法があります。
__iter__
and__next__
(or next
Python 2.x))__getitem__
)例:
# generator
def uc_gen(text):
for char in text.upper():
yield char
# generator expression
def uc_genexp(text):
return (char for char in text.upper())
# iterator protocol
class uc_iter():
def __init__(self, text):
self.text = text.upper()
self.index = 0
def __iter__(self):
return self
def __next__(self):
try:
result = self.text[self.index]
except IndexError:
raise StopIteration
self.index += 1
return result
# getitem method
class uc_getitem():
def __init__(self, text):
self.text = text.upper()
def __getitem__(self, index):
return self.text[index]
4つのメソッドすべての動作を確認するには:
for iterator in uc_gen, uc_genexp, uc_iter, uc_getitem:
for ch in iterator('abcde'):
print(ch, end=' ')
print()
その結果:
A B C D E
A B C D E
A B C D E
A B C D E
注:
2つのジェネレーターのタイプ(uc_gen
およびuc_genexp
)はできませんreversed()
。単純なイテレータ(uc_iter
)には__reversed__
魔法のメソッドが必要になります(docsによれば、新しいイテレータを返す必要がありますが、self
(少なくともCPythonでは)機能します)。そしてgetitem iteratable(uc_getitem
)は__len__
魔法のメソッドを持っていなければなりません:
# for uc_iter we add __reversed__ and update __next__
def __reversed__(self):
self.index = -1
return self
def __next__(self):
try:
result = self.text[self.index]
except IndexError:
raise StopIteration
self.index += -1 if self.index < 0 else +1
return result
# for uc_getitem
def __len__(self)
return len(self.text)
無限に遅延評価された反復子に関するパニック大佐の二次的な質問に答えるために、上記の4つの方法のそれぞれを使用した例を次に示します。
# generator
def even_gen():
result = 0
while True:
yield result
result += 2
# generator expression
def even_genexp():
return (num for num in even_gen()) # or even_iter or even_getitem
# not much value under these circumstances
# iterator protocol
class even_iter():
def __init__(self):
self.value = 0
def __iter__(self):
return self
def __next__(self):
next_value = self.value
self.value += 2
return next_value
# getitem method
class even_getitem():
def __getitem__(self, index):
return index * 2
import random
for iterator in even_gen, even_genexp, even_iter, even_getitem:
limit = random.randint(15, 30)
count = 0
for even in iterator():
print even,
count += 1
if count >= limit:
break
print
その結果(少なくとも私のサンプル実行では):
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30
0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32
どれを使用するかを選択するには?これは主に好みの問題です。私が最もよく目にする2つの方法は、ジェネレーターとイテレータープロトコル、およびハイブリッド(__iter__
ジェネレーターを返す)です。
ジェネレータ式は、リスト内包表記を置き換えるのに役立ちます(それらは遅延するため、リソースを節約できます)。
以前のPython 2.xバージョンとの互換性が必要な場合は、を使用してください__getitem__
。
uc_iter
終了するとインスタンスの有効期限が切れます(それ以外の場合は無期限になります)。もう一度実行したい場合は、再度呼び出して新しいイテレータを取得する必要がありますuc_iter()
。
self.index = 0
で__iter__
あなたは何度も繰り返すことができるように。そうでなければできません。
まず、itertoolsモジュールは、イテレーターが役立つあらゆる種類のケースで非常に便利ですが、Pythonでイテレーターを作成するために必要なものはすべて次のとおりです。
産出
かっこよくありませんか?Yieldは、関数の通常の戻り値を置き換えるために使用できます。オブジェクトはまったく同じように返されますが、状態を破棄して終了するのではなく、次の反復を実行するときのために状態を保存します。以下はitertools関数リストから直接引かれた動作の例です:
def count(n=0):
while True:
yield n
n += 1
関数の説明(itertoolsモジュールのcount()関数です)で述べたように、nで始まる連続した整数を返すイテレータを生成します。
ジェネレーター式は、他の完全なワーム(素晴らしいワーム!)です。彼らはの代わりに使用することができるリスト読解リストの内包表記は、変数に割り当てられていない場合は、使用後に破壊されたメモリ内のリストを作成しますが、ジェネレータ式はジェネレータオブジェクトを作成することができます(メモリを節約するために...の空想の方法ですイテレータと言います)。ジェネレータ式の定義の例を次に示します。
gen = (n for n in xrange(0,11))
これは、全範囲が0〜10の間で事前に決定されていることを除いて、上記のイテレータ定義と非常に似ています。
私はxrange()を見つけて(私はこれまでに見たことがないと思います...)、それを上記の例に追加しました。 xrange()はrange()の反復可能なバージョンであり、リストを事前に作成しないという利点があります。反復する巨大なデータのコーパスがあり、それを実行するのに大量のメモリしかない場合は、非常に便利です。
私はあなたのいくつかは、やって見るreturn self
の中で__iter__
。私はちょうどことに注意することは望んでいた__iter__
(これの必要性を取り除く自体が発電することができ__next__
、および調達StopIteration
例外)
class range:
def __init__(self,a,b):
self.a = a
self.b = b
def __iter__(self):
i = self.a
while i < self.b:
yield i
i+=1
もちろん、ここでは直接ジェネレータを作成することもできますが、より複雑なクラスの場合は便利です。
return self
で書くのはつまらない__iter__
。私がyield
それを使ってみるつもりだったとき、私はあなたのコードが私がやりたいことを正確に実行しているのを見つけました。
next()
ますか?return iter(self).next()
?
self.current
や他のカウンターを追跡する必要はありません。これがトップ投票の答えになるはずです!
iter
しますが、それ自体はクラスのインスタンスではありません。
この質問は、反復子についてではなく、反復可能なオブジェクトについてです。Pythonでは、シーケンスも反復可能であるため、反復可能クラスを作成する1つの方法は、シーケンスのように動作させること、つまり、シーケンス__getitem__
と__len__
メソッドを提供することです。これをPython 2および3でテストしました。
class CustomRange:
def __init__(self, low, high):
self.low = low
self.high = high
def __getitem__(self, item):
if item >= len(self):
raise IndexError("CustomRange index out of range")
return self.low + item
def __len__(self):
return self.high - self.low
cr = CustomRange(0, 10)
for i in cr:
print(i)
__len__()
メソッドは必要ありません。 __getitem__
期待される動作を備えた単独で十分です。
このページのすべての回答は、複雑なオブジェクトに非常に適しています。しかし、属性として反復可能なタイプの組み込みを含有するもののために、のようなstr
、list
、set
またはdict
、またはのいずれかの実装ではcollections.Iterable
、あなたのクラスで、特定の物事を省略することができます。
class Test(object):
def __init__(self, string):
self.string = string
def __iter__(self):
# since your string is already iterable
return (ch for ch in self.string)
# or simply
return self.string.__iter__()
# also
return iter(self.string)
次のように使用できます。
for x in Test("abcde"):
print(x)
# prints
# a
# b
# c
# d
# e
return iter(self.string)
。
これは、なしの反復可能な関数yield
です。iter
関数と、list
Python 2を囲むスコープ内の変更可能な状態()で状態を保持するクロージャーを利用します。
def count(low, high):
counter = [0]
def tmp():
val = low + counter[0]
if val < high:
counter[0] += 1
return val
return None
return iter(tmp, None)
Python 3の場合、クロージャーの状態はエンクロージングスコープの不変に保持nonlocal
され、ローカルスコープで状態変数を更新するために使用されます。
def count(low, high):
counter = 0
def tmp():
nonlocal counter
val = low + counter
if val < high:
counter += 1
return val
return None
return iter(tmp, None)
テスト;
for i in count(1,10):
print(i)
1
2
3
4
5
6
7
8
9
iter
ていますが、明確にするために:これは、yield
ベースのジェネレーター関数を使用するよりも複雑で効率が悪いです。Pythonには、yield
ここでは利用できないベースジェネレーター関数に対する多数のインタープリターサポートがあり、このコードを大幅に遅くしています。それでも賛成投票。
ここでマット・グレゴリーの答えに触発されて、a、b、...、z、aa、ab、...、zz、aaa、aab、...、zzy、zzzを返すもう少し複雑なイテレータがあります
class AlphaCounter:
def __init__(self, low, high):
self.current = low
self.high = high
def __iter__(self):
return self
def __next__(self): # Python 3: def __next__(self)
alpha = ' abcdefghijklmnopqrstuvwxyz'
n_current = sum([(alpha.find(self.current[x])* 26**(len(self.current)-x-1)) for x in range(len(self.current))])
n_high = sum([(alpha.find(self.high[x])* 26**(len(self.high)-x-1)) for x in range(len(self.high))])
if n_current > n_high:
raise StopIteration
else:
increment = True
ret = ''
for x in self.current[::-1]:
if 'z' == x:
if increment:
ret += 'a'
else:
ret += 'z'
else:
if increment:
ret += alpha[alpha.find(x)+1]
increment = False
else:
ret += x
if increment:
ret += 'a'
tmp = self.current
self.current = ret[::-1]
return tmp
for c in AlphaCounter('a', 'zzz'):
print(c)