2015-02-05 70 views
5

我有一個關於如何從給定列表創建子列表(我希望這是正確的術語來使用)而不復制的問題。Python:創建子列表而不復制

看來,切片可以創建子列表,但它與複製。這是一個例子。

In [1]: a = [1,2,3] 

In [2]: id(a) 
Out[2]: 4354651128 

In [3]: b = a[0:2] 

In [4]: b 
Out[4]: [1, 2] 

In [5]: id(b) 
Out[5]: 4354621312 

In [6]: id(a[0:2]) 
Out[6]: 4354620880 

請看這裏b和a [0:2]的id雖然不同,但它們的值是相同的。要仔細檢查,更改a中的值,b中的值不會更改。

In [7]: a[1] = 4 

In [8]: a 
Out[8]: [1, 4, 3] 

In [9]: b 
Out[9]: [1, 2] 

所以要回我的問題,我怎麼可以創建子列表,但沒有複製?我的意思是,當a [1]的值設置爲4時,b將是[1,4]。

我周圍搜索,並沒有找到太多的幫助(也許我沒有使用正確的關鍵字)。謝謝!


編輯:

謝謝大家對您的意見和解答!這是我所學到的。

  • 在Python中沒有內置的方式來創建列表視圖(或不創建子列表而不復制)。
  • 最簡單的方法是使用numpy數組。
  • 雖然numpy的數組數據類型的限制與列表進行比較,它確實爲我的目的(實現沒有多餘的內存快速排序)

這裏是numpy的陣列相同的過程。

In [1]: import numpy as np 

In [2]: a = np.arange(1,4) 

In [3]: a 
Out[3]: array([1, 2, 3]) 

In [4]: b = a[0:2] 

In [5]: b 
Out[5]: array([1, 2]) 

In [6]: id(b) 
Out[6]: 4361253952 

In [7]: id(a[0:2]) 
Out[7]: 4361254032 

In [8]: a[1] = 4 

In [9]: a 
Out[9]: array([1, 4, 3]) 

In [10]: b 
Out[10]: array([1, 4]) 
+1

這種共享的問題是內存泄漏:假設您使用對列表和值a和b的引用來表示切片列表[a:b]。然後,即使切片非常小,它也會阻止列表被垃圾收集,這可能會非常昂貴。但是,當然,您可以使用上述表示法爲「符號」列表切片定義自定義類。 – 2015-02-05 22:07:59

+0

你爲什麼要這樣做? – 2015-02-05 22:47:53

+1

我想你所描述的非常接近'numpy'陣列的觀點。看到[這個SO帖子和答案](http://stackoverflow.com/questions/4370745/view-onto-a-numpy-array)關於這個話題的一些討論。但要注意,與典型的Python列表相比,'numpy'數組對於它們可以包含的數據類型不太靈活,所以它們可能不適合您的用例,具體取決於您希望包含的數據。 – zehnpaard 2015-02-06 01:30:32

回答

4

numpy的對象數組支持創建相互依賴子列表,這種概念通過具有切片返回views而不是數據的副本。

更改原始的numpy數組將改變從數組創建的視圖,並且對任何視圖的更改也會反映到原始數組中。特別是對於大型數據集,視圖是以不同方式切割數據的好方法,同時節省內存。

>>> import numpy as np 
>>> array1 = np.array([1, 2, 3, 4]) 
>>> view1 = array1[1:] 
>>> view1 
array([2, 3, 4]) 
>>> view1[1] = 5 
>>> view1 
array([2, 5, 4]) 
>>> array1 
array([1, 2, 5, 4]) # Notice that the change to view1 has been reflected in array1 

爲了進一步參考,請參閱numpy documentation on views以及this SO post

+0

想象我重新發明了輪子,很好的答案。 – mVChr 2015-02-07 01:44:25

1

沒有內置的方式做到這一點。您可以創建自己的類列表類,它引用列表並重新實現所有列表訪問器方法以對其進行操作。

1

無法使用內置的Python數據結構來完成此操作。但是,我創建了一個能夠滿足您需要的課程。我不保證它沒有錯誤,但它應該讓你開始。

from itertools import islice 

class SubLister(object): 
    def __init__(self, base=[], start=0, end=None): 
     self._base = base 
     self._start = start 
     self._end = end 

    def __len__(self): 
     if self._end is None: 
      return len(self._base) - self._start 
     return self._end - self._start 

    def __getitem__(self, index): 
     self._check_end_range(index) 
     return self._base[index + self._start] 

    def __setitem__(self, index, value): 
     self._check_end_range(index, "list assignment index out of range") 
     self._base[index + self._start] = value 

    def __delitem__(self, index): 
     self._check_end_range(index, "list assignment index out of range") 
     del self._base[index + self._start] 

    def __iter__(self): 
     return islice(self._base, self._start, self._end) 

    def __str__(self): 
     return str(self._base[self._start:self._end]) 

    def __repr__(self): 
     return repr(self._base[self._start:self._end]) 

    # ...etc... 

    def get_sublist(self, start=0, end=None): 
     return SubLister(base=self._base, start=start, end=end) 

    def _check_end_range(self, index, msg="list index out of range"): 
     if self._end is not None and index >= self._end - self._start: 
      raise IndexError(msg) 

實施例:

>>> from sublister import SubLister 
>>> base = SubLister([1, 2, 3, 4, 5]) 
>>> a = base.get_sublist(0, 2) 
>>> b = base.get_sublist(1) 

>>> base 
[1, 2, 3, 4, 5] 
>>> a 
[1, 2] 
>>> b 
[2, 3, 4, 5] 
>>> len(base) 
5 
>>> len(a) 
2 
>>> len(b) 
4 

>>> base[1] = 'ref' 
>>> base 
[1, 'ref', 3, 4, 5] 
>>> a 
[1, 'ref'] 
>>> b 
['ref', 3, 4, 5] 
+0

這是一個很好的實現,但有幾個方法仍然複製列表,這是不受歡迎的(如len和iter)。 – Dunes 2015-02-06 15:49:06

+0

感謝您在編輯@Dunes中的修復,事實證明它確實像我提到的那樣是越野車。我只是想給這個人一個開始,他可以合作。 – mVChr 2015-02-06 17:35:19

+0

我不會說我做的修改是bug修復。代碼在功能上是正確的。更多的是提高班級的效率,這是爲了儘量減少名單的複製。 – Dunes 2015-02-07 01:06:03