为什么Python 3中的切片仍然是复制而不是视图?

59 投票
2 回答
8053 浏览
提问于 2025-04-16 22:40

我刚刚注意到,在评论了这个回答后,Python 3中的切片返回的是浅拷贝,而不是视图。为什么会这样呢?即使不考虑numpy使用视图而不是拷贝的情况,Python 3中的dict.keysdict.valuesdict.items都返回视图,而且Python 3还有很多其他方面都更倾向于使用迭代器,这让人觉得切片应该也朝这个方向发展。虽然itertools有一个islice函数可以进行迭代切片,但它的功能比普通切片要有限,而且没有像dict.keysdict.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 个回答

15

另外,你可以通过对切片进行赋值来修改原始列表,但切片本身是副本而不是视图,这一点也很重要。

嗯……这说得不太对;虽然我能理解你为什么会这么想。在其他编程语言中,像这样的切片赋值:

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作为一个简单的标识符出现,它只能表示一个变量赋值;视图就丢失了。

6

看起来我找到了很多关于这个观点的原因,主要是从一个讨论串开始的,链接在这里: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那样,正如zipmap等在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列表扩展那样把结果存储在原数组中。

撰写回答