2013-10-22 18 views
10

我正在使用Python 2.7.3。參數解包是否使用迭代或項目獲取?

考慮使用自定義的虛擬類(雖然壞)迭代和獲取項目行爲:

class FooList(list): 
    def __iter__(self): 
     return iter(self) 
    def next(self): 
     return 3 
    def __getitem__(self, idx): 
     return 3 

做一個例子,看到了怪異的行爲:

>>> zz = FooList([1,2,3]) 

>>> [x for x in zz] 
# Hangs because of the self-reference in `__iter__`. 

>>> zz[0] 
3 

>>> zz[1] 
3 

但現在,讓我們做一個功能然後在zz上進行參數解包:

def add3(a, b, c): 
    return a + b + c 

>>> add3(*zz) 
6 
# I expected either 9 or for the interpreter to hang like the comprehension! 

因此,參數un打包以某種方式獲取來自zz的項目數據,但不是通過迭代該對象及其實現的迭代器,也不是通過做一個可憐的人的迭代器,並且爲對象所具有的項目調用__getitem__

所以問題是:如果不是這些方法,語法add3(*zz)如何獲取zz的數據成員?我是否錯過了另一種從這種類型獲取數據成員的常見模式?

我的目標是看看我是否可以編寫一個實現迭代或項目獲取的類,以這種方式改變參數拆包語法對該類的意義。在嘗試了上述兩個示例之後,我現在想知道如何解開參數來獲取底層數據以及程序員是否可以影響該行爲。谷歌爲此僅回饋了大量的結果,解釋了*args語法的基本用法。

我沒有一個用例需要做到這一點,我不認爲這是一個好主意。我只是想看看如何爲了好奇而做到這一點。

新增

由於內置類型都經過特殊處理,在這裏與object一個例子,我只是保持一個列表對象,並實現自己的獲取和設置的行爲模仿名單。

class FooList(object): 
    def __init__(self, lst): 
     self.lst = lst 
    def __iter__(self): raise ValueError 
    def next(self): return 3 
    def __getitem__(self, idx): return self.lst.__getitem__(idx) 
    def __setitem__(self, idx, itm): self.lst.__setitem__(idx, itm) 

在這種情況下,

In [234]: zz = FooList([1,2,3]) 

In [235]: [x for x in zz] 
--------------------------------------------------------------------------- 
ValueError        Traceback (most recent call last) 
<ipython-input-235-ad3bb7659c84> in <module>() 
----> 1 [x for x in zz] 

<ipython-input-233-dc9284300db1> in __iter__(self) 
     2  def __init__(self, lst): 
     3   self.lst = lst 
----> 4  def __iter__(self): raise ValueError 
     5  def next(self): return 3 
     6  def __getitem__(self, idx): return self.lst.__getitem__(idx) 

ValueError: 

In [236]: add_3(*zz) 
--------------------------------------------------------------------------- 
ValueError        Traceback (most recent call last) 
<ipython-input-236-f9bbfdc2de5c> in <module>() 
----> 1 add_3(*zz) 

<ipython-input-233-dc9284300db1> in __iter__(self) 
     2  def __init__(self, lst): 
     3   self.lst = lst 
----> 4  def __iter__(self): raise ValueError 
     5  def next(self): return 3 
     6  def __getitem__(self, idx): return self.lst.__getitem__(idx) 

ValueError: 

但是相反,如果我保證迭代停止,總是返回3,我能得到什麼我拍攝在第一種情況下玩弄:

class FooList(object): 
    def __init__(self, lst): 
     self.lst = lst 
     self.iter_loc = -1 
    def __iter__(self): return self 
    def next(self): 
     if self.iter_loc < len(self.lst)-1: 
      self.iter_loc += 1 
      return 3 
     else: 
      self.iter_loc = -1 
      raise StopIteration 
    def __getitem__(self, idx): return self.lst.__getitem__(idx) 
    def __setitem__(self, idx, itm): self.lst.__setitem__(idx, itm) 

然後我看到這一點,這是我最初的預期:

In [247]: zz = FooList([1,2,3]) 

In [248]: ix = iter(zz) 

In [249]: ix.next() 
Out[249]: 3 

In [250]: ix.next() 
Out[250]: 3 

In [251]: ix.next() 
Out[251]: 3 

In [252]: ix.next() 
--------------------------------------------------------------------------- 
StopIteration        Traceback (most recent call last) 
<ipython-input-252-29d4ae900c28> in <module>() 
----> 1 ix.next() 

<ipython-input-246-5479fdc9217b> in next(self) 
    10   else: 
    11    self.iter_loc = -1 
---> 12    raise StopIteration 
    13  def __getitem__(self, idx): return self.lst.__getitem__(idx) 
    14  def __setitem__(self, idx, itm): self.lst.__setitem__(idx, itm) 

StopIteration: 

In [253]: ix = iter(zz) 

In [254]: ix.next() 
Out[254]: 3 

In [255]: ix.next() 
Out[255]: 3 

In [256]: ix.next() 
Out[256]: 3 

In [257]: ix.next() 
--------------------------------------------------------------------------- 
StopIteration        Traceback (most recent call last) 
<ipython-input-257-29d4ae900c28> in <module>() 
----> 1 ix.next() 

<ipython-input-246-5479fdc9217b> in next(self) 
    10   else: 
    11    self.iter_loc = -1 
---> 12    raise StopIteration 
    13  def __getitem__(self, idx): return self.lst.__getitem__(idx) 
    14  def __setitem__(self, idx, itm): self.lst.__setitem__(idx, itm) 

StopIteration: 

In [258]: add_3(*zz) 
Out[258]: 9 

In [259]: zz[0] 
Out[259]: 1 

In [260]: zz[1] 
Out[260]: 2 

In [261]: zz[2] 
Out[261]: 3 

In [262]: [x for x in zz] 
Out[262]: [3, 3, 3] 

摘要

  1. 語法*args僅依賴於迭代。對於內置類型,這種方式不能直接在從內置類型繼承的類中被覆蓋。

  2. 這兩個在功能上等效:

    foo(*[x for x in args])

    foo(*args)

  3. 這些都不是即使對於有限的數據結構等效。

    foo(*args)

    foo(*[args[i] for i in range(len(args))])

+1

我想你會發現更有趣的信息,如果你從'object'而不是'list'派生'FooList'。 –

+2

這不會因爲無限迭代而掛起;它會因爲__iter __(self)以遞歸方式間接調用自己而掛起,所以它甚至需要無限次遞歸_get_迭代器。你可以完全刪除'next'並獲得相同的行爲...... – abarnert

+0

我還創建了一個單獨的'FooListIter'類,並且'__iter__'返回了一個實例,具有與上面相同的'next'行爲,並且它也執行了相同的操作。但是,謝謝你提到'iter'在這裏自稱。 – ely

回答

9

你被Python的最惱人的疣一個被咬:內建類型以及它們的子類是在一些地方神奇治療。

由於您的類型list的子類,Python神奇地到達其內部以解壓縮它。它根本不使用真正的迭代器API。如果您在next__getitem__中插入print語句,您會看到沒有人被調用。這種行爲不能被覆蓋;相反,你將不得不編寫自己的類來重新實現內建類型。你可以嘗試使用UserList;我沒有檢查是否會起作用。

您的問題的答案是,參數拆包使用迭代。但是,如果沒有明確的__iter__定義,迭代本身可以使用__getitem__。你不能創建一個定義與正常迭代行爲不同的參數解包行爲的類。

不應該假定迭代器協議(基本上「__iter__工作原理」)如何適用於子類型list等內置類型的類型。如果你創建了一個內建子類,那麼在某些情況下,你的子類可能神奇地表現得像內置的內建類,而不使用自定義的魔法方法(如__iter__)。如果你想完全可靠地自定義行爲,你不能從內建類型繼承子類(當然,object除外)。

+0

因此對於'list','tuple'和'dict ',我不能打斷任何內部被調用來獲取數據成員在參數拆包過程中? – ely

+2

@EMS:你爲什麼想要?如果你不想'list' /'tuple' /'dict'魔法行爲,不要從它們繼承;你可以保留一個'list' /'tuple' /'dict'成員並委託給它(正好在你想要的地方,而不是你不想要的地方)。 – abarnert

+0

我沒有說我想要。 – ely

相關問題