【问题标题】:How to implement __iter__(self) for a container object (Python)如何为容器对象实现 __iter__(self) (Python)
【发布时间】:2011-04-30 12:24:20
【问题描述】:

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

根据this page,我需要在我的对象上实现这个方法:

__iter__(self)

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

有人可以发布一个 sn-p(或指向资源的链接),说明如何做到这一点吗?

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

for k, v in mydict.items()

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

【问题讨论】:

    标签: python


    【解决方案1】:

    我通常会使用生成器函数。每次使用 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?这如何区分何时停止?
    • @JonathanLeaders 当some_list 中的所有元素都已生成时。
    • 实际上——在这个用例中——你只需要在 beforesome_list 用尽时停止产生值时提高 StopIteration。
    • StopIteration 在生成器函数返回时由 Python 自动引发,通过显式调用 return 或到达函数末尾(与所有函数一样,在结束)。显式提高StopIteration 不是必需的,并且从Python 3.5 开始实际上将不起作用(请参阅PEP 479):就像生成器将return 转换为StopIteration,它们将显式raise StopIteration 转换为RuntimeError。跨度>
    【解决方案2】:

    通常__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

    【讨论】:

    • 这就是你对迭代器类所做的,但问题是关于容器对象。
    • 在 Python 3 及更高版本中仍然如此吗?
    • “next”方法的名称前后应有 2 个下划线
    【解决方案3】:

    如果您的对象包含一组您想将对象的迭代器绑定到的数据,您可以作弊并这样做:

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

    【讨论】:

    • 不要检查hasattr,而是使用try/except AttributeError
    【解决方案4】:

    另一种选择是从 `collections 模块中的适当抽象基类继承 here

    如果容器是它自己的迭代器,你可以继承自 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
    

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

    【讨论】:

    • 不要混淆可迭代对象和迭代器——你不想从迭代器继承不是迭代器本身的可迭代对象(例如容器)。
    • @Glenn:典型的容器不是迭代器是正确的。我刚刚关注了这个问题,其中提到了迭代器类型。正如我在答案末尾所说,我认为从更合适的选项继承更合适。我将在答案中澄清这一点。
    • 似乎使用 .pop() 来实现下一个函数会导致一些不良影响。首先,迭代器以相反的顺序返回可迭代对象的元素,其次,可发音对象在迭代时被耗尽。还有其他方便的方法吗?
    【解决方案5】:

    要回答关于映射的问题:您提供的__iter__ 应该遍历映射的。以下是一个简单的示例,它创建了一个映射x -> x * x,并在 Python3 上扩展了 ABC 映射。

    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
    

    【讨论】:

      【解决方案6】:

      一种可能适用于某些情况的选项是让您的自定义类继承来自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
      

      【讨论】:

        【解决方案7】:

        如果您不想像其他人建议的那样从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
        

        这使用了一个生成器,很好地描述了here

        由于我们继承自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)
        

        【讨论】:

          【解决方案8】:

          inhert from dict的例子,修改它的iter,例如在for循环中跳过键2

          # 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
          

          【讨论】:

            【解决方案9】:

            python中的“可迭代接口”由__next__()__iter__()两个方法组成。 __next__ 函数是最重要的,因为它定义了迭代器的行为——也就是说,函数决定了接下来应该返回什么值。 __iter__() 方法用于重置迭代的起点。很多时候,你会发现__iter__()在用__init__()设置起点时,就可以直接返回self了。

            请参见下面的代码来定义一个类 Reverse,它实现了“可迭代接口”并在任何序列类的任何实例上定义了一个迭代器。 __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
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2017-10-11
              • 2021-04-30
              • 2013-02-14
              • 2017-05-24
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-07-05
              相关资源
              最近更新 更多