为什么Python 3中的切片仍然是复制而不是视图?
我刚刚注意到,在评论了这个回答后,Python 3中的切片返回的是浅拷贝,而不是视图。为什么会这样呢?即使不考虑numpy使用视图而不是拷贝的情况,Python 3中的dict.keys
、dict.values
和dict.items
都返回视图,而且Python 3还有很多其他方面都更倾向于使用迭代器,这让人觉得切片应该也朝这个方向发展。虽然itertools
有一个islice
函数可以进行迭代切片,但它的功能比普通切片要有限,而且没有像dict.keys
或dict.values
那样的视图功能。
另外,你可以通过赋值来修改原始列表的切片,但切片本身是拷贝而不是视图,这在语言上有点矛盾,似乎违背了Python之禅中提到的几个原则。
也就是说,你可以这样做:
>>> a = [1, 2, 3, 4, 5]
>>> a[::2] = [0, 0, 0]
>>> a
[0, 2, 0, 4, 0]
但不能这样:
>>> a = [1, 2, 3, 4, 5]
>>> a[::2][0] = 0
>>> a
[0, 2, 3, 4, 5]
或者类似这样的:
>>> a = [1, 2, 3, 4, 5]
>>> b = a[::2]
>>> b
view(a[::2] -> [1, 3, 5]) # numpy doesn't explicitly state that its slices are views, but it would probably be a good idea to do it in some way for regular Python
>>> b[0] = 0
>>> b
view(a[::2] -> [0, 3, 5])
>>> a
[0, 2, 3, 4, 5]
这看起来有点随意/不太理想。
我知道http://www.python.org/dev/peps/pep-3099/中提到的内容,里面说“切片和扩展切片不会消失(即使__getslice__
和__setslice__
的API可能会被替换),也不会为标准对象类型返回视图。”但是,相关讨论并没有提到为什么决定切片不使用视图;实际上,原帖中关于这个建议的评论大多数都是正面的。
是什么阻止了这样的设计在Python 3.0中实现呢?Python 3.0特别设计为不严格向后兼容Python 2.x,这本来是实施这种设计变更的最佳时机,未来的Python版本中还有什么可能会阻止这样的改变吗?
2 个回答
另外,你可以通过对切片进行赋值来修改原始列表,但切片本身是副本而不是视图,这一点也很重要。
嗯……这说得不太对;虽然我能理解你为什么会这么想。在其他编程语言中,像这样的切片赋值:
a[b:c] = d
是等同于
tmp = a.operator[](slice(b, c)) # which returns some sort of reference
tmp.operator=(d) # which has a special meaning for the reference type.
但在Python中,第一个语句实际上会被转换成这样:
a.__setitem__(slice(b, c), d)
这就是说,在Python中,项的赋值会被特别识别,具有特殊的含义,这与查找项和赋值是分开的;它们可能没有关系。这与Python整体的设计是一致的,因为Python没有像C/C++那样的“左值”概念;你无法重载赋值操作符本身;只有在赋值的左侧不是普通标识符的特定情况下才可以。
假设列表确实有视图;如果你尝试使用它:
myView = myList[1:10]
yourList = [1, 2, 3, 4]
myView = yourList
在Python以外的语言中,可能有办法将yourList
放入myList
中,但在Python中,由于myView
作为一个简单的标识符出现,它只能表示一个变量赋值;视图就丢失了。
看起来我找到了很多关于这个观点的原因,主要是从一个讨论串开始的,链接在这里:http://mail.python.org/pipermail/python-3000/2006-August/003224.html(这个讨论主要是关于字符串切片的,但至少有一封邮件提到了像列表这样的可变对象),还有一些内容来自:
http://mail.python.org/pipermail/python-3000/2007-February/005739.html
http://mail.python.org/pipermail/python-dev/2008-May/079692.html 以及后续的邮件
看起来,如果把基础Python的风格改成这种方式,带来的复杂性和各种不太好的边缘情况会大大超过它的好处。唉。
...然后我开始想,是否可以用一种可迭代的方式来替代现在处理slice
对象的方式,就像itertools.islice
那样,正如zip
、map
等在Python 3中返回可迭代对象而不是列表一样,我开始意识到这样做可能会带来很多意想不到的行为和问题。看起来这可能暂时是个死胡同。
好的一面是,numpy的数组相当灵活,所以在需要这种情况时,使用一维的ndarray代替列表并不会太难。不过,似乎ndarray不支持像Python列表那样使用切片在数组中插入额外的项目:
>>> a = [0, 0]
>>> a[:1] = [2, 3]
>>> a
[2, 3, 0]
我认为numpy的等价物应该是这样的:
>>> a = np.array([0, 0]) # or a = np.zeros([2]), but that's not important here
>>> a = np.hstack(([2, 3], a[1:]))
>>> a
array([2, 3, 0])
一个稍微复杂一点的例子:
>>> a = [1, 2, 3, 4]
>>> a[1:3] = [0, 0, 0]
>>> a
[1, 0, 0, 0, 4]
与之相比
>>> a = np.array([1, 2, 3, 4])
>>> a = np.hstack((a[:1], [0, 0, 0], a[3:]))
>>> a
array([1, 0, 0, 0, 4])
当然,上面的numpy例子并不会像普通Python列表扩展那样把结果存储在原数组中。