Python 防止在迭代时修改自定义类

Python 防止在迭代时修改自定义类,python,iterator,concurrentmodification,Python,Iterator,Concurrentmodification,如果我有一个带有接口的类: class AnIteratable(object): def __init__(self): #initialize data structure def add(self, obj): # add object to data structure def __iter__(self): #return the iterator def next(self): # return next object …我该

如果我有一个带有接口的类:

class AnIteratable(object):

  def __init__(self):
    #initialize data structure

  def add(self, obj):
    # add object to data structure

  def __iter__(self):
    #return the iterator

  def next(self):
    # return next object
…我该如何设置,以便在迭代中期调用
add()
,会出现异常,类似于:

In [14]: foo = {'a': 1}

In [15]: for k in foo:
   ....:     foo[k + k] = 'ohnoes'
   ....:     
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-15-2e1d338a456b> in <module>()
----> 1 for k in foo:
      2     foo[k + k] = 'ohnoes'
      3 

RuntimeError: dictionary changed size during iteration
更新3 在阅读了更多内容后,似乎
\uu del\uu
不是正确的选择。下面可能是一个更好的解决方案,尽管它要求用户显式地释放一个未耗尽的迭代器

    def next(self):
      if self._notExhausted and 
              self._iterable._datastructure.hasNext(self._currentIndex):
      #same as above from here

    def discard(self):
      if self._notExhausted:
        self._ostore._itercount -= 1
      self._notExhausted = False

您不应该将迭代器与实例混合使用。否则,如果您希望一次对实例进行多次迭代,会发生什么情况

考虑一下迭代器的位置存储在哪里

    def next(self):
      if self._notExhausted and 
              self._iterable._datastructure.hasNext(self._currentIndex):
      #same as above from here

    def discard(self):
      if self._notExhausted:
        self._ostore._itercount -= 1
      self._notExhausted = False
将迭代器拆分为单独的类。在创建迭代器实例时存储对象的大小。每当调用
next()
时,请检查大小

dicts
也不是万无一失的。您可以添加和删除一个键,该键将破坏迭代,但不会抛出错误

Python 2.7.3 (default, Aug  1 2012, 05:14:39) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> d = {i:i for i in range(3)}
>>> d
{0: 0, 1: 1, 2: 2}
>>> for k in d:
...     d[k+3] = d.pop(k)
...     print d
... 
{1: 1, 2: 2, 3: 0}
{2: 2, 3: 0, 4: 1}
{3: 0, 4: 1, 5: 2}
{4: 1, 5: 2, 6: 0}
{5: 2, 6: 0, 7: 1}
{6: 0, 7: 1, 8: 2}
{7: 1, 8: 2, 9: 0}
{8: 2, 9: 0, 10: 1}
{9: 0, 10: 1, 11: 2}
{10: 1, 11: 2, 12: 0}
{11: 2, 12: 0, 13: 1}
{12: 0, 13: 1, 14: 2}
{13: 1, 14: 2, 15: 0}
{16: 1, 14: 2, 15: 0}
{16: 1, 17: 2, 15: 0}
{16: 1, 17: 2, 18: 0}

超过3次迭代

如果项目是可索引的且具有长度,则可以执行类似于
dict
的操作:

class AnIterable(list):

    def __iter__(self):
         n = len(self)
         i = 0
         while i < len(self):
             if len(i) != n:
                 raise RuntimeError("object changed size during iteration")
             yield self[i]
             i += 1
由于必须在每个可以修改列表的方法中增加修订计数器,因此这会变得很混乱。我想,您可以编写一个元类或类装饰器来自动为列表编写这样的包装器方法

另一种方法是保持“活动”迭代器的计数,在创建迭代器时递增实例属性,在迭代器耗尽时递减实例属性。然后在
add()
中,检查以确保此属性为零,如果不是,则引发异常

class AnIterable(object):

    def __init__(self, iterable=()):
        self._itercount = 0
        self._content   = list(iterable)

    def __iter__(self):
         self._itercount += 1
         try:
             for x in self._content:
                 yield x
         finally:
             self._itercount -= 1

    def add(self, obj):
        if self._itercount:
            raise RuntimeError("cannot change object while iterating")
        self._content.append(obj)
为了获得额外的积分,在迭代器上实现
\uuu del\uuu()
,这样当对象超出范围而未耗尽时,计数也会减少。(注意双重递减!)这需要定义您自己的自定义迭代器类,而不是使用Python在函数中使用
yield
时提供的迭代器类,当然也不能保证在任何情况下何时调用
\uu del\uu()

唉,不管你加了什么“保护”,你都无法真正阻止某人四处走动。我们都是成年人

在任何情况下都不能使用
self
作为迭代器

    def next(self):
      if self._notExhausted and 
              self._iterable._datastructure.hasNext(self._currentIndex):
      #same as above from here

    def discard(self):
      if self._notExhausted:
        self._ostore._itercount -= 1
      self._notExhausted = False
最后,使用一种不同的、或多或少相反的方法:您让调用方进行更改,但推迟实际应用更改,直到迭代完成。上下文管理器用于明确地完成更改


为了确保调用者使用上下文管理器,如果您不在上下文中,您可以拒绝迭代(例如,签入
\uuuuu iter\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu()
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu(例如,在每个迭代器中设置一个标志,以便在下一次迭代时引发异常)。

您如何实现
next
?如果
foo
是一个
dict
,您可以对foo.keys()中的k使用
。在另一种情况下,这取决于
下一个
方法的实现。@DavidRobinson我不是-这只是一个接口。我想知道,对于具有任意数据结构的任意类,您会怎么做,因为我假设这种情况相当普遍。我特别喜欢这种方法能够检测到len的任何更改容器对象的gth(即删除,即使OP在其示例代码中没有用于该操作的接口)。唯一的问题可能是,它似乎假设容器是可索引的。当然,如果您在迭代之间添加和删除一个项,这将不会抓住这一点……关于实时迭代器思想的实现:我认为您犯的错误与我在回答中犯的错误相同(我已删除)在这一点上,您假设生成器将一直被消耗。我不完全确定,但似乎在一个interator被放弃的情况下,
\u itercount
不会被正确地递减。是的,这个示例有这个缺陷。这就是为什么我说“为了获得额外积分,实现”\u del\u()。您需要编写自己的迭代器类,我不想调试它。:-)是的,我想这基本上就是@gnibbler在回答中所说的(创建一个单独的迭代器类)。如果我有时间并且感觉很有动力,我可能会尝试一下……毕竟,这似乎是一个基本的迭代器问题,需要遵循一个配方或模式。