【问题标题】:Equivalent code of __getitem__ in __iter____iter__ 中 __getitem__ 的等效代码
【发布时间】:2016-08-03 07:17:02
【问题描述】:

我正在尝试更多地了解 Python 3 中的 __iter__。出于某种原因,我对 __getitem__ 的理解比 __iter__ 更好。我想我不知何故没有得到相应的 next 实现,然后是__iter__

我有以下代码:

class Item:
    def __getitem__(self,pos):
        return range(0,30,10)[pos]

item1= Item()
print (f[1]) # 10
for i in item1:
   print (i) # 0 10 20

我理解上面的代码,但我又该如何使用__iter____next__() 编写等效代码?

class Item:
   def __iter__(self):
      return self
      #Lost here
   def __next__(self,pos):
      #Lost here

我知道当 python 看到 __getitem__ 方法时,它会尝试通过调用具有以 0 开头的整数索引的方法来迭代该对象。

【问题讨论】:

  • 在真实清单上试一试。 mylist.__iter__ 函数返回一个生成器。您可以通过调用my_generator.next() 几次来通过此生成器执行一些步骤。但是我不会称它为等价,因为列表项访问是恒定时间,而迭代直到某个点是线性时间。
  • def __iter__(self): return (x for x in range(0,30,10)) 呢?
  • @machineyearning 我也需要传递一个索引。 (自我,位置)
  • 不确定你的意思。
  • 在上面的代码中,我其实也想传递一个索引参数。例如。如果我想实现类似 print(f[1]) 的东西,1 是我想传递给方法的索引。

标签: python python-3.x iterator


【解决方案1】:

一般来说,一个非常好的方法是通过yielding 值使__iter__ 成为生成器。这可能不太直观,但它是直截了当的;您只需返回您想要的结果,然后自动为您提供__next__

class Item:
   def __iter__(self):
      for item in range(0, 30, 10):
          yield item

这只是使用yield 的力量来获得所需的效果,当Python 在您的对象上调用__iter__ 时,它期望返回一个iterator(即支持__next__ 调用的对象),a生成器就是这样做的,当调用__next__ 时,生成生成器函数中定义的每个项目(即在这种情况下为__iter__):

>>> i = iter(Item())    
>>> print(i)  # generator, supports __next__
<generator object __iter__ at 0x7f6aeaf9e6d0>
>>> next(i)
0
>>> next(i)
10
>>> next(i)
20

现在您获得与__getitem__ 相同的效果。不同之处在于没有传入index,您必须手动循环它才能产生结果:

>>> for i in Item():
...    print(i)    
0
10
20

除此之外,还有另外两种方法可以创建支持迭代的对象。

一次循环:使项目成为迭代器

在这种情况下,通过定义__next__ 并从__iter__ 返回self,使Item 成为迭代器,因为您没有使用yield,所以__iter__ 方法返回self__next__ 句柄返回值的逻辑:

class Item:

   def __init__(self):
      self.val = 0

   def __iter__(self):
      return self

   def __next__(self):
      if self.val > 2: raise StopIteration
      res = range(0, 30, 10)[self.val]
      self.val += 1
      return res

这也使用了一个辅助 val 来从范围中获取结果并检查我们是否应该继续迭代(如果不是,我们提出 StopIteration):

>>> for i in Item():
...    print(i)
0
10
20

这种方法的问题是它是一次性的,在迭代一次之后,self.val 指向3,并且无法再次执行迭代。 (使用yield 解决了这个问题)。 (是的,您可以将 val 设置为 0,但这只是偷偷摸摸。)

多次循环:创建自定义迭代器对象。

第二种方法是专门为您的Item 类使用自定义迭代器对象,并从Item.__iter__ 而不是self 返回它:

class Item:
    def __iter__(self):
        return IterItem()


class IterItem:
   def __init__(self):
      self.val = 0
   def __iter__(self):
      return self
   def __next__(self):
      if self.val > 2: raise StopIteration
      res = range(0, 30, 10)[self.val]
      self.val += 1
      return res

现在每次迭代时都会提供一个新的自定义迭代器,您可以支持对 Item 对象的多次迭代。

【讨论】:

  • 我喜欢你全面的回答,以及你抽烟的骷髅头像。
【解决方案2】:

Iter 返回一个迭代器,主要是 @machineyearning 在 cmets 告诉的生成器,接下来您可以迭代对象,请参见示例:

class Item:
    def __init__(self):
        self.elems   = range(10)
        self.current = 0

    def __iter__(self):
        return (x for x in self.elems)

    def __next__(self):
        if self.current >= len(self.elems):
            self.current = 0
            raise StopIteration
        return self.elems[self.current]

>>> i = Item()
>>> a = iter(i)
>>> for x in a:
...     print x
...     
0
1
2
3
4
5
6
7
8
9
>>> for x in i:
...     print x
...     
0
1
2
3
4
5
6
7
8
9

【讨论】:

  • 如果您的__iter__ 方法返回的不是self,那么使用__next__ 方法是不合适的。同时拥有基于 __iter____next__ 的生成器表示您不确定是否应该成为迭代器。
猜你喜欢
  • 2013-12-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-23
  • 2014-02-03
  • 2014-11-15
相关资源
最近更新 更多