636

Pythonで反復関数(または反復子オブジェクト)を作成するにはどうすればよいですか?

4

9 に答える 9

719

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」は、かなり良い入門書です。

于 2008-08-23T16:57:28.637 に答える
486

反復関数を作成するには、次の 4 つの方法があります。

  • ジェネレーターを作成します ( yield キーワードを使用します)
  • ジェネレーター式 ( genexp )を使用する
  • イテレータを作成します (and を定義します__iter__(__next__またはnextPython 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__selfuc_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__.

于 2011-09-24T22:13:44.493 に答える
121

でやっている人もい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

もちろん、ここではジェネレータを直接作成することもできますが、より複雑なクラスでは便利です。

于 2012-07-27T15:05:12.317 に答える
105

まず第一に、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() の反復可能なバージョンでリストを事前に構築しないという利点があります。反復するデータの巨大なコーパスがあり、それを実行するためのメモリが非常に多い場合、非常に便利です。

于 2008-08-21T00:36:33.050 に答える
3

これは、なしで反復可能な関数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
于 2016-03-03T17:55:26.140 に答える