【问题标题】:Return a non iterator from __iter__从 __iter__ 返回一个非迭代器
【发布时间】:2018-06-15 21:45:59
【问题描述】:
class test(object):
    def __init__(self):
        pass
    def __iter__(self):
        return "my string"

o = test()
print iter(o)

为什么这会给出回溯?

$ python iter_implement.py
Traceback (most recent call last):
  File "iter_implement.py", line 9, in <module>
    print iter(o)
TypeError: iter() returned non-iterator of type 'str'

我希望__iter__ 在这种情况下只返回字符串。何时以及为什么检测到返回的对象不是迭代器对象?

【问题讨论】:

    标签: python


    【解决方案1】:

    str 是一个iterable 而不是一个iterator,微妙但重要的区别。请参阅此answer 以获得解释。

    您想返回一个带有 __next__ 的对象(或者如果 py2 则只是 next),这是 str 在迭代时返回的内容。

    def __iter__(self):
          return iter("my string")
    

    str 没有实现__next__

    In [139]: s = 'mystring'
    
    In [140]: next(s)
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-140-bc0566bea448> in <module>()
    ----> 1 next(s)
    
    TypeError: 'str' object is not an iterator
    

    然而调用 iter 会返回迭代器,这就是循环调用的:

    In [141]: next(iter(s))
    Out[141]: 'm'
    

    如果没有 __next__(或 py2 中的 next)方法返回任何内容,您也会遇到同样的问题

    您可以使用生成器,它本身具有返回self__iter__

    def gen():
        yield 'foo'
    
    gg = gen()
    
    gg is gg.__iter__()
    True
    
    gg.__next__()
    'foo'
    
    class Something:
         def __iter__(self):
             return gen()
    
    list(Something())
    ['foo']
    

    或者您自己实现__next__ 的类,例如类似于Ops 帖子中的类(您还必须处理停止循环的StopIteration

    class test:
        def __init__(self, somestring):
            self.s = iter(somestring)
    
        def __iter__(self):
            return self
    
        def __next__(self):
            return next(self.s) ## this exhausts the generator and raises StopIteration when done.
    
    In [3]: s = test('foo')
    
    In [4]: for i in s:
       ...:     print(i)
       ...:
    f
    o
    o
    

    【讨论】:

    • 问题不是如何修复代码。问题是为什么会出错?我知道可迭代和迭代器之间的区别。
    • @abc 很抱歉,您知道其中的区别,但指出这一点肯定会让其他人受益。另外,添加了一个解释。
    【解决方案2】:

    __iter__ 魔法函数的目的是返回一些你可以迭代(例如循环)的东西。最常见的解决方案是返回iter(something),其中something 可以是列表、元组、集合、字典、字符串……任何我们可以迭代的东西。看看这个例子:

    class Band:
        def __init__(self):
            self.members = []
    
        def add_member(self, name):
            self.members.append(name)
    
        def __iter__(self):
            return iter(self.members)
    
    if __name__ == '__main__':
        band = Band()
        band.add_member('Peter')
        band.add_member('Paul')
        band.add_member('Mary')
    
        # Magic of __iter__:
        for member in band:
            print(member)
    

    输出:

    Peter
    Paul
    Mary
    

    在这种情况下,__iter__ 魔术函数允许我们循环遍历band,就好像它是成员的集合一样。这意味着在您的情况下, return "my string" 不会。如果您想要“我的字符串”中的字符列表:

    def __iter__(self):
        return iter("my string")  # ==> m, y, ' ', s, t, r, i, n, g
    

    但是,如果你想返回一个包含单个元素“我的字符串”的列表,那么:

    def __iter__(self):
        return iter(["my string"])
    

    【讨论】:

      【解决方案3】:

      回答您的具体问题。 Python2 似乎在检查是否存在 .next 类属性:

      >>> class test(object):
      ...     next = None
      ...     def __iter__(self):
      ...         return self
      ... 
      >>> print iter(test())
      <__main__.test object at 0x7fcef75c2f50>
      

      实例属性不行:

      >>> class test(object):
      ...    def __init__(self):
      ...        self.next = None
      ...    def __iter__(self):
      ...        return self
      ... 
      >>> print iter(test())
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      TypeError: iter() returned non-iterator of type 'test'
      

      【讨论】:

      • 很有趣,你和 Raymond Hettinger 一起回答了我的实际问题。
      【解决方案4】:

      可以通过添加iter()调用来修复代码:

      class test(object):
          def __init__(self):
              pass
          def __iter__(self):
              return iter("my string")
      

      这是一个示例运行:

      >>> o = test()
      >>> iter(o)
      <iterator object at 0x106bfa490>
      >>> list(o)
      ['m', 'y', ' ', 's', 't', 'r', 'i', 'n', 'g']
      

      原始错误的原因是 __iter__ 的 API 声称返回一个实际的迭代器。 iter() 函数检查以确保合同得到履行。

      注意,这种错误检查也发生在其他地方。例如,len() 函数检查以确保 __len__() 方法返回一个整数:

      >>> class A:
              def __len__(self):
                  return 'hello'
      
      >>> len(A())
      Traceback (most recent call last):
        File "<pyshell#4>", line 1, in <module>
          len(A())
      TypeError: 'str' object cannot be interpreted as an integer
      

      【讨论】:

      • iter() 函数检查以确保合同得到履行。 ...虽然不是很彻底 ;-) 好吧,可能足以抓住奇怪的脚拍摄。
      猜你喜欢
      • 2021-02-17
      • 2017-03-19
      • 2010-09-23
      • 1970-01-01
      • 1970-01-01
      • 2020-07-20
      • 2021-10-15
      • 1970-01-01
      • 2017-04-16
      相关资源
      最近更新 更多