2015-02-05 83 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