3

インターフェイスを持つクラスがある場合:

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

更新: インターフェイスにさらにメソッドが必要な場合は、自由に追加してください。の実装も削除しました__iter__()

更新#2 kindallの回答に基づいて、次の疑似実装をモックアップしました。_datastruture とそれにインデックスを付ける関連メソッドは抽象化であり、クラスの作成者は独自のデータ構造トラバーサルとロケーション ポインター メカニズムを記述する必要があることに注意してください。

class AnIteratable(object):

  def __init__(self):
    self._itercount = 0
    self._datastructure = init_data_structure() #@UndefinedVariable
    # _datastructure, and the methods called on it, are abstractions.

  def add(self, obj):
    if self._itercount:
      raise RuntimeError('Attempt to change object while iterating')
    # add object to data structure

  def __iter__(self):
    self._itercount += 1
    return self.AnIterator(self)

  class AnIterator(object):

    def __init__(self, aniterable):
      self._iterable = aniterable
      self._currentIndex = -1 #abstraction
      self._notExhausted = True

    def next(self):
      if self._iterable._datastructure.hasNext(self._currentIndex):
        self._currentIndex += 1
        return self._iterable._datastructure.next(self._currentIndex)
      else:
        if self._notExhausted:
          self._iterable._itercount -= 1
        self._notExhausted = False
        raise StopIteration

    def __next__(self):
      return self.next()

    # will be called when there are no more references to this object
    def __del__(self): 
      if self._notExhausted:
        self._iterable._itercount -= 1

Update 3 もう少し読んだ後、__del__おそらく正しい方法ではないようです。以下はより良い解決策かもしれませんが、ユーザーは使い果たされていない反復子を明示的に解放する必要があります。

    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
4

2 に答える 2

3

イテレータとインスタンスを混在させないでください。そうしないと、一度に複数回インスタンスを反復したいときにどうなりますか?

イテレータの位置を格納する場所を考えてください。

イテレータを別のクラスに分割します。イテレータ インスタンスを作成するときに、オブジェクトのサイズを格納します。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回以上の反復!

于 2012-09-18T01:35:30.700 に答える
1

アイテムがインデックス可能で長さがある場合は、次のようにすることができます。これは、次のように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

欠点は、呼び出し元が長さに実質的な変更をもたらさない複数の変更を行った場合 (たとえば、要素を追加してから削除するなど)、キャッチされないことです。もちろん、長さをチェックするだけでなく、リビジョン カウンター (他のメソッドが変更を加えるたびにインクリメントされる) を使用することもできます。

class AnIterable(object):

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

    def __iter__(self):
        r = self._rev
        for x in self._content:
            if self._rev != r:
                 raise RuntimeError("object changed during iteration")
            yield x

    def add(self, item):
        self._content.append(item)
        self._rev += 1

リストを変更できる各メソッドでリビジョンカウンターをインクリメントする必要があるため、これは面倒です。メタクラスまたはクラス デコレータを記述して、リストのそのようなラッパー メソッドを自動的に記述することができると思います。

もう 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)

ボーナス ポイントとして、イテレータに実装__del__()して、オブジェクトが使い尽くされずにスコープ外に出たときにもカウントが減少するようにします。(二重デクリメントに注意してください!) これには、関数で使用するときに Python が提供するものを使用するのではなく、独自のカスタム イテレータ クラスを定義する必要があり、もちろん、いつ呼び出されるかについてyieldの保証はありません。__del__()

残念ながら、追加した「保護」が何であれ、誰かが回避するのを本当に止めることはできません。私たちは皆、ここで同意する大人です。

どのような場合でもできないことはself、イテレータとして使用することです。

最後に、別の、多かれ少なかれ正反対のアプローチの例を示します。変更は呼び出し元に任せますが、実際に適用するのは反復が完了するまで延期します。変更を明示的に確定するために、コンテキスト マネージャーが使用されます。

呼び出し元が確実にコンテキスト マネージャーを使用するようにするために、コンテキスト内にない場合は反復処理を拒否し (たとえば、 に__iter__()設定されたフラグをチェックイン__enter__())、イテレータ オブジェクトのリストを保存し、コンテキストを終了するときにそれらを無効にします (たとえば、各反復子にフラグを設定して、次の反復で例外が発生するようにします)。

于 2012-09-18T01:50:44.673 に答える