如何为容器对象实现__iter __(self)(Python)


115

我已经编写了一个自定义容器对象。

根据此页面,我需要在我的对象上实现此方法:

__iter__(self)

但是,在跟踪Python参考手册中指向Iterator Types的链接时,没有给出如何实现自己的示例。

有人可以发布一个片段(或链接到资源)来显示如何执行此操作吗?

我正在写的容器是一个映射(即通过唯一键存储值)。dict可以这样迭代:

for k, v in mydict.items()

在这种情况下,我需要能够在迭代器中返回两个元素(一个元组?)。尚不清楚如何实现这样的迭代器(尽管已经提供了好几个答案)。有人可以进一步说明如何为类似地图的容器对象实现迭代器吗?(即充当字典的自定义类)?

Answers:


120

我通常会使用一个生成器函数。每次使用yield语句时,都会在序列中添加一个项目。

下面的代码将创建一个迭代器,该迭代器生成五个,然后生成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?这如何区分何时停止?
乔纳森(Jonathan)

1
@JonathanLeaders当所有元素some_list都已屈服时。
laike9m 2015年

实际上-在此用例中-仅在希望 some_list精疲力尽之前停止产生值时才需要提高StopIteration 。
Tim Peoples

21
StopIteration当生成器函数返回时,Python会自动引发它,方法是通过显式调用return或到达函数的末尾(就像所有函数return None的末尾都隐式一样)。明确地提出StopIteration是没有必要的,像Python 3.5的将其实并不工作(见PEP 479):就像发电机转returnStopIteration,他们把明确raise StopIterationRuntimeError
亚瑟·塔卡

28

另一种选择是从适当的抽象基类继承自`集合模块记录在这里

如果容器是其自己的迭代器,则可以从继承 collections.Iterator。您只需要实现该next方法即可。

一个例子是:

>>> 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

注意:感谢Glenn Maynard提请我注意需要澄清一方面迭代器与另一方面是可迭代容器而不是迭代器的容器之间的区别。


13
不要混淆可迭代的对象和迭代器-您不想从Iterator继承非迭代器本身(例如容器)的可迭代对象。
格伦·梅纳德

@Glenn:您是正确的,典型的容器不是迭代器。我只是关注了一个提到迭代器类型的问题。我认为从答案更合适的选项中继承是更合适的。我将在答案中阐明这一点。
Muhammad Alkarouri 2010年

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
这就是您对迭代器类所做的事情,但是问题是关于容器对象的。
格伦·梅纳德

11

如果您的对象包含一组要绑定对象迭代器的数据,则可以作弊并执行以下操作:

>>> 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
不用检查hasattr,而是使用try/except AttributeError
IceArdor

9

在“可迭代的接口”的python由两个方法__next__()__iter__()。该__next__函数是最重要的,因为它定义了迭代器的行为-也就是说,该函数确定下一步应返回什么值。该__iter__()方法用于重置迭代的起点。通常,您会发现,__iter__()__init__()用于设置起点时, 它只能返回自身。

请参阅以下代码,以定义实现Reitable接口的类反向,并定义任何序列类中任何实例的迭代器。该__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

在某些情况下可能有效的一种选择是使您的自定义类继承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,例如,2在for循环中跳过键

# 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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.