2017-06-06 49 views
0

在namedtuple上有定製迭代器的pythonic方法嗎?在namedtuple上定製迭代器

自定義迭代器似乎需要索引屬性。通常,一個namedtuple有一個「__slots__ =()」聲明,它可以避免動態字典的開銷並減少屬性查找的延遲。但是,這會破壞迭代器(AttributeError: 'Path' object has no attribute '_iidx')。將_iidx添加到插槽結果nonempty __slots__ not supported

上下文:我將與大量的貝塞爾樣條一起工作。每個樣條由一系列「結點」和控制點對組成。通過評論__slots__ =()聲明,我的代碼有效。但是,以每個實例動態字典爲代價。與其他所有內容相比,屬性查找中字典的開銷可能可以忽略不計,但它看起來並不是pythonic。

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格式化平凡。這:

print('<path d="%s" stroke="black" stroke-width="1" fill="blue" />' 
     % path.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() 

注: '節' 是一個List[Point] and'ctrl_pts'is a 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

不能在namedtuple上使用非空'__slots__'。它導致'非空__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

爲了使你的代碼工作原樣,將_iidx添加到對namedtuple()的調用中的屬性列表中。

請注意,繼承自namedtuple類使得進一步的子類不太有用,因爲繼承者無法添加新屬性。這可能會或可能不會影響您的應用程序。作爲替代方案,您可以從object繼承並在__slots__中調出屬性名稱。這將保留繼承自namedtuple的大部分性能優勢,並使繼承者可以在其__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

我喜歡zip解決方案。非常棒!請忽略先前的評論。當我試圖添加一個時,它被髮送,我編輯它太慢了。 –