Pythonで反復関数(または反復子オブジェクト)を作成するにはどうすればよいですか?
9 に答える
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)
印刷出力は同じになります。内部的には、ジェネレータオブジェクトはイテレータプロトコルをサポートし、クラスCounterとほぼ同様のことを行います。
DavidMertzの記事「IteratorsandSimpleGenerators」は、かなり良い入門書です。
反復関数を作成するには、次の 4 つの方法があります。
- ジェネレーターを作成します ( yield キーワードを使用します)
- ジェネレーター式 ( genexp )を使用する
- イテレータを作成します (and を定義します
__iter__
(__next__
またはnext
Python 2.x では)) - Python が独自に繰り返し処理できるクラスを作成します ( define
__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
が必要です(ドキュメントによると、新しいイテレータを返す必要がありますが、(少なくとも CPython では) 動作を返します)。getitem iteratable ( ) にはマジック メソッドが必要です。__reversed__
self
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__
.
でやっている人もい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
もちろん、ここではジェネレータを直接作成することもできますが、より複雑なクラスでは便利です。
まず第一に、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() の反復可能なバージョンで、リストを事前に構築しないという利点があります。反復するデータの巨大なコーパスがあり、それを実行するためのメモリが非常に多い場合、非常に便利です。
これは、なしで反復可能な関数yield
です。関数と、 Python 2 のエンクロージング スコープ内 iter
のミュータブル () に状態を保持するクロージャーを利用します。list
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