コンテナオブジェクトに__iter __(self)を実装する方法(Python)


115

カスタムコンテナオブジェクトを記述しました。

このページによると、私は自分のオブジェクトにこのメソッドを実装する必要があります:

__iter__(self)

ただし、PythonリファレンスマニュアルのIterator Typesへのリンクをたどると、独自の実装方法の例はありません。

これを行う方法を示すスニペット(またはリソースへのリンク)を誰かが投稿できますか?

私が書いているコンテナはマップです(つまり、一意のキーによって値を格納します)。辞書は次のように繰り返すことができます:

for k, v in mydict.items()

この場合、イテレーターで2つの要素(タプル?)を返すことができる必要があります。そのようなイテレータを実装する方法はまだ明確ではありません(親切に提供されたいくつかの回答にもかかわらず)。マップのようなコンテナオブジェクトのイテレータを実装する方法についてもう少し詳しく説明してもらえますか?(つまり、dictのように機能するカスタムクラス)?

回答:


120

通常はジェネレーター関数を使用します。収量ステートメントを使用するたびに、アイテムがシーケンスに追加されます。

次の例では、5を返すイテレータを作成し、次にsome_listのすべての項目を作成します。

def __iter__(self):
   yield 5
   yield from some_list

3.3より前のバージョンyield fromは存在しなかったため、次のようにする必要があります。

def __iter__(self):
   yield 5
   for x in some_list:
      yield x

StopIterationを上げる必要がありますか?これはいつ停止するかをどのように区別しますか?
ジョナサン

1
@JonathanLeadersのすべての要素some_listが生成されたとき。
laike9m 2015年

実際-この使用例では- が使い果たされる前に 値の生成を停止したい場合にのみ、StopIterationを上げる必要がありますsome_list
Tim Peoples

21
StopIteration明示的に呼び出すreturnか、関数の最後に到達すると(すべての関数return Noneの最後に暗黙的な関数があるように)、ジェネレーター関数が戻るときにPythonによって自動的に発生します。明示的に発生させるStopIteration必要はなく、Python 3.5以降は実際には機能しません(PEP 479を参照):ジェネレーターがに変わるようreturnStopIteration、明示的raise StopIterationにに変わりRuntimeErrorます。
Arthur Tacca

28

別のオプションは、ここで説明されているように、 `collectionsモジュールから適切な抽象基本クラスを継承することです

コンテナが独自のイテレータである場合、から継承できます collections.Iteratornextその後、メソッドを実装するだけで済みます。

例は次のとおりです。

>>> from collections import Iterator
>>> class MyContainer(Iterator):
...     def __init__(self, *data):
...         self.data = list(data)
...     def next(self):
...         if not self.data:
...             raise StopIteration
...         return self.data.pop()
...         
...     
... 
>>> c = MyContainer(1, "two", 3, 4.0)
>>> for i in c:
...     print i
...     
... 
4.0
3
two
1

あなたが見ている間collections、モジュール、継承を検討しSequenceMappingそれがより適切であるかどうか、別の抽象基本クラス。次にSequenceサブクラスの例を示します。

>>> from collections import Sequence
>>> class MyContainer(Sequence):
...     def __init__(self, *data):
...         self.data = list(data)
...     def __getitem__(self, index):
...         return self.data[index]
...     def __len__(self):
...         return len(self.data)
...         
...     
... 
>>> c = MyContainer(1, "two", 3, 4.0)
>>> for i in c:
...     print i
...     
... 
1
two
3
4.0

NB:一方のイテレータと、もう一方のイテレータではなくイテラブルであるコンテナの違いを明確にする必要性に注意を向けてくれたGlenn Maynardに感謝します。


13
反復可能オブジェクトと反復子を混同しないでください。反復子自体ではない反復可能オブジェクト(例:コンテナー)の反復子から継承したくない場合。
Glenn Maynard、

@Glenn:典型的なコンテナーはイテレーターではないというのは正しいことです。イテレータのタイプについての質問に答えました。答えの最後で述べたように、より適切なオプションから継承する方が適切だと思います。回答の中でこの点を明らかにします。
Muhammad Alkarouri、

13

通常__iter__()、next()メソッド(ジェネレーターオブジェクト)を既に定義している場合は、単にselfを返します。

ここにジェネレータのダミーの例があります:

class Test(object):

    def __init__(self, data):
       self.data = data

    def next(self):
        if not self.data:
           raise StopIteration
        return self.data.pop()

    def __iter__(self):
        return self

ただし、次の__iter__()ように使用することもできます:http : //mail.python.org/pipermail/tutor/2006-January/044455.html


7
これがイテレータクラスに対して行うことですが、問題はコンテナオブジェクトについてです。
Glenn Maynard、

11

オブジェクトに、オブジェクトのiterをバインドしたいデータのセットが含まれている場合は、チートしてこれを行うことができます。

>>> class foo:
    def __init__(self, *params):
           self.data = params
    def __iter__(self):
        if hasattr(self.data[0], "__iter__"):
            return self.data[0].__iter__()
        return self.data.__iter__()
>>> d=foo(6,7,3,8, "ads", 6)
>>> for i in d:
    print i
6
7
3
8
ads
6

2
をチェックする代わりにhasattrtry/except AttributeError
IceArdor

9

Pythonの「反復可能なインターフェース」は、2つのメソッド__next__()とで構成されてい__iter__()ます。__next__それはイテレータの振る舞いを定義としての機能は、最も重要である-で、この関数は値は次の返却すべきかを決定します。この__iter__()メソッドは、反復の開始点をリセットするために使用されます。多くの場合、開始点を設定するためにを使用すると、__iter__()自分自身を返すことができることがわかります __init__()

「反復可能インターフェース」を実装し、任意のシーケンスクラスの任意のインスタンスに対してイテレータを定義するクラスリバースの定義については、次のコードを参照してください。この__next__()メソッドはシーケンスの最後から始まり、シーケンスの逆の順序で値を返します。「シーケンスインターフェース」を実装するクラスのインスタンスは、__len__()および__getitem__()メソッドを定義する必要があることに注意してください。

class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, seq):
        self.data = seq
        self.index = len(seq)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]

>>> rev = Reverse('spam')
>>> next(rev)   # note no need to call iter()
'm'
>>> nums = Reverse(range(1,10))
>>> next(nums)
9

7

マッピングに関する質問に答えるには、提供さ__iter__れたものがマッピングのキーを反復処理する必要があります。以下は、マッピングを作成しx -> x * x、ABCマッピングを拡張するPython3で機能する簡単な例です。

import collections.abc

class MyMap(collections.abc.Mapping):
    def __init__(self, n):
        self.n = n

    def __getitem__(self, key): # given a key, return it's value
        if 0 <= key < self.n:
            return key * key
        else:
            raise KeyError('Invalid key')

    def __iter__(self): # iterate over all keys
        for x in range(self.n):
            yield x

    def __len__(self):
        return self.n

m = MyMap(5)
for k, v in m.items():
    print(k, '->', v)
# 0 -> 0
# 1 -> 1
# 2 -> 4
# 3 -> 9
# 4 -> 16

4

dict他の人が示唆しているように継承したくない場合__iter__は、カスタム辞書の大まかな例を実装する方法に関する質問に対する直接の回答を次に示します。

class Attribute:
    def __init__(self, key, value):
        self.key = key
        self.value = value

class Node(collections.Mapping):
    def __init__(self):
        self.type  = ""
        self.attrs = [] # List of Attributes

    def __iter__(self):
        for attr in self.attrs:
            yield attr.key

これは、ここで詳しく説明さているジェネレータを使用します

から継承しているのでMapping、実装__getitem__して__len__次のことも行う必要があります。

    def __getitem__(self, key):
        for attr in self.attrs:
            if key == attr.key:
                return attr.value
        raise KeyError

    def __len__(self):
        return len(self.attrs)

2

場合によっては機能するオプションの1つは、カスタムクラスから継承することですdict。これは、dictのように機能する場合、論理的な選択のように見えます。多分それはすべきであることのdict。このようにして、dictのようなイテレーションを無料で入手できます。

class MyDict(dict):
    def __init__(self, custom_attribute):
        self.bar = custom_attribute

mydict = MyDict('Some name')
mydict['a'] = 1
mydict['b'] = 2

print mydict.bar
for k, v in mydict.items():
    print k, '=>', v

出力:

Some name
a => 1
b => 2

2

dictからの継承のiter例、そのを変更、たとえば2forループのときにキーをスキップ

# method 1
class Dict(dict):
    def __iter__(self):
        keys = self.keys()
        for i in keys:
            if i == 2:
                continue
            yield i

# method 2
class Dict(dict):
    def __iter__(self):
        for i in super(Dict, self).__iter__():
            if i == 2:
                continue
            yield i
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.