2017-06-06 12 views
0

名前付きタプルにカスタムイテレータを持つためのpythonic方法はありますか?名前付きタプルのカスタムイテレータ

カスタムイテレータには、インデックス属性が必要なようです。通常、名前付きタプルには "__slots__ =()"という宣言があり、これは動的辞書のオーバーヘッドを回避し、属性検索の待ち時間を短縮します。ただし、これによりイテレータ(AttributeError: 'Path' object has no attribute '_iidx')が破損します。スロットに_iidxを追加すると、nonempty __slots__ not supported

コンテキスト:私は多数のベジェスプラインで作業します。各スプラインは、「結び目」点のリストと制御点のペアのリストで構成されています。 __slots__ =()宣言をコメントアウトすると、私のコードが動作します。しかし、インスタンスごとに動的なdictを犠牲にして。属性ルックアップのdictからのオーバヘッドは、おそらく他のすべてに比べて無視できる程度ですが、pythonのようには見えません。

class Path(namedtuple('Path', "knots ctrl_pts")): 
# __slots__ =() 

    @property 
    def SVG(self): 
     s = 'M %s' % self.knots[0].bare 
     for cps, k in self: 
      s += ' C %s %s' % (cps.bare, k.bare) 
     return s 

    def __iter__(self): 
     self._iidx = 0 
     return self 

    def __next__(self): 
     if self._iidx == len(self.ctrl_pts): 
      raise StopIteration 
     i = self._iidx 
     self._iidx += 1 
     return (self.ctrl_pts[i], self.knots[i+1]) 

SVGメソッドでは、イテレータを使用してSVGを簡単に作成します。この:

<path d="M 318.9 179.4 C 279.1 177.6 199.2 270.3 222.4 298.1 C 245.5 
326.0 371.6 289.1 420.4 297.0 C 469.2 304.9 440.6 357.6 399.6 352.4 
C 388.5 350.9 389.7 345.0 400.4 347.6 C 440.1 357.4 469.4 309.2 419.6 
303.0 C 369.8 296.7 240.9 332.5 217.6 301.9 C 194.4 271.2 276.9 174.1 
319.1 174.6 C 330.3 174.7 329.9 179.9 318.9 179.4" stroke="black" 
stroke-width="1" fill="blue" /> 

しかし、またGtk.DrawingAreaのon_draw(self, wid, cr):に使用するのは簡単です::

 path = self.coords.outline_path 
     cr.set_source_rgb(*LT_BLUE) 
     cr.move_to(*path.knots[0]) 
     for (c,k) in path: 
      cr.curve_to(*c.P1, *c.P2, *k) 
     cr.fill_preserve() 
     cr.set_source_rgb(*BLACK) 
     cr.stroke() 

注: 'ノット' である中

print('<path d="%s" stroke="black" stroke-width="1" fill="blue" />' 
     % path.SVG) 

結果List[Point]、「ctrl_pts」はList[ControlPoints]

class Point(namedtuple('Point', "x y")): 
    __slots__ =() 
    """<bunch of stuff not shown>""" 
    @property 
    def bare(self): 
     return '%.1f %.1f' % (self.x, self.y) 

class ControlPoints(namedtuple('ControlPoints', "P1 P2")): 
    __slots__ =() 
    @property 
    def bare(self): 
     return '%s %s' % (self.P1.bare, self.P2.bare) 
です。

私は非常にPythonに新しいですし、これはもっとpythonicの方法で行うことができると確信しています。

+0

「__slots__」は使用しないでください。 – BrenBarn

+0

名前付きタプルに空でない '__slots__'を使用することはできません。 'nonempty __slots__はサポートされていません ' –

+0

私は' __slots__'を使用していないのですか?なぜ__dict__を受け入れないのですか? – BrenBarn

答えて

1

イテレータプロトコルを手動で実装するのではなく、__iter__を目的の値のジェネレータにします。ジェネレータオブジェクトは、イテレータプロトコルを実装します。

class Path(namedtuple('Path', "knots ctrl_pts")): 

    @property 
    def SVG(self): 
     s = 'M %s' % self.knots[0].bare 
     for cps, k in self: 
      s += ' C %s %s' % (cps.bare, k.bare) 
     return s 

    def __iter__(self): 
     for t in zip(self.ctrl_pts, self.knots[1:]): 
      yield t 

あなただけ__iter__SVGの一部として使用されることが予想される場合は、あなたが本当にそれを必要としない、そしてこのようなものを使用することができます:あるとして、あなたのコードを動作させるために

class Path(namedtuple('Path', "knots ctrl_pts")): 

    @property 
    def SVG(self): 
     return 'M %s%s' % (self.knots[0].bare, 
          ''.join([' C %s %s' % (cps.bare, k.bare) 
            for cps, k in zip(self.ctrl_pts, self.knots[1:])])) 
+0

それを愛し、素晴らしい作品! –

1

を、 namedtuple()の呼び出しで属性リストに_iidxを追加してください。

名前付きタプルクラスから継承すると、継承者が新しい属性を追加できないため、さらに細かいサブクラス化が役立ちません。これはあなたのアプリケーションでは重要ではないかもしれません。代わりにobjectから継承し、__slots__の属性名を呼び出すこともできます。これにより、名前付きタプルから継承することによるパフォーマンス上の利点の多くが保持され、継承者が__slots__に新しい属性を定義できるようになります。以下

変更点は以下のとおりです。

  1. __iter__は、今はもう_iidxを必要としないので、あなたのためのループを管理ジェネレータを返します。イテレータを作成する興味深い方法については、itertoolsを参照してください。 __next__を定義することは、必要以上に多くの作業になります。
  2. svgプロパティは、文字列をリストに累積して結合します。これは通常、strのインクリメント演算子よりも高速です。
  3. 最後のビットは、svgプロパティを小文字に変更しています。これは純粋なスタイルですが、コア言語のstyle guideと一直線になっています。

from itertools import izip # python 2 only 

class Path(namedtuple('Path', "knots ctrl_pts")): 
    __slots__ =() 

    @property 
    def svg(self): # attribute names are usually lower case 
     # accumulating strings with += is slow 
     # use a list insted 
     s = ['M %s' % self.knots[0].bare] 
     for cps, k in self: 
      s.append('C %s %s' % (cps.bare, k.bare)) 
     return ' '.join(s) 

    def __iter__(self): 
     # python 3 
     return zip(self.ctrl_pts, self.knots[1:]) 

     # python 2 
     return izip(self.ctrl_pts, self.knots[1:]) 
+0

残念なことに、namedtuple 'の場合、フィールド名はアンダースコアで始めることはできません: '_iidx' '。アンダースコアを削除すると、__new__が失敗します。 –

+0

私はジップソリューションが大好きです。よく働く!!以前のコメントは無視してください。 を追加しようとしたときに送信されましたが、編集が遅すぎました。 –

関連する問題