第11条 学会对列表切片
Python 有这样一种写法,可以从序列里面切割出一片内容,让我们能够轻松获取原序列的某个子集合。最简单的用法就是切割内置的 list、str与bytes。其实,凡事实现了__ getitem __ 和 __ setitem __ 这两个特殊方法的类都可以实现切割(参见 第43条)。
最基本的方法是 somelist[start: end]这一个形式切割,也就是从 start 开始一直取到 end 这个位置,但不包含 end 本身这个元素。
a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
print('Middle two: ', a[3:5])
print('All but ends:', a[1:7])
>>>
Middle two: ['d', 'e']
All but ends: ['b', 'c', 'd', 'e', 'f', 'g']
如果是从头开始切割列表,那就应该省略冒号左侧的下标0,这样看起来更加清晰。
assert a[:5] == a[0:5]
如果一直取到列表末尾,那就应该省略冒号右侧的下标,因为用不着专门把它写出来。
assert a[5:] == a[5:len(a)]
用负数做下标表示从列表尾部往前算。-k,就是倒数第 k 个位置。换算成不带符号的下标,就是 len(a) - k。
print(a[:]) # ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
print(a[:5]) # ['a', 'b', 'c', 'd', 'e']
print(a[:-1]) # ['a', 'b', 'c', 'd', 'e', 'f', 'g']
print(a[4:]) # ['e', 'f', 'g', 'h']
print(a[-3:]) # ['f', 'g', 'h']
print(a[2:5]) # ['c', 'd', 'e']
print(a[2:-1]) # ['c', 'd', 'e', 'f', 'g']
print(a[-3:-1]) # ['f', 'g']
如果起点与终点所确定的范围超过了列表的边界,那么系统会自动忽略不存在的元素。利用这个特性,很容易构造出一个最多只有若干元素的输入序列,例如:
first_twenty_items = a[:20] # ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
last_twenty_items = a[-20:] # ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
切割时所用的下标可以越界,但是直接访问列表却不行,那样会让程序抛出异常。
print(a[20])
>>>
Traceback ...
IndexError: list index out of range
注:用下标来切割列表时,只有个别情况才会出现奇怪的效果。只要 n 大于等于1, somelist[-n:] 总是可以切割出你想要的切片。只有当 n 为0的时候,才需要特别注意,此时 somelist[-0:] 其实相当于 somelist[0:] ,所以和 somelist[:] 一样,会制作出原列表的一份副本。
切割出来的是一份全新的列表。即使把某个元素换掉,也不会影响原列表中的相应位置。那个位置上的还是旧值。
a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
b = a[3:]
print('Before: ', b)
b[1] = 99
print('After: ', b)
print('No change: ', a)
>>>
Before: ['d', 'e', 'f', 'g', 'h']
After: ['d', 99, 'f', 'g', 'h']
No change: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
切片可以出现在赋值符号的左侧,表示右侧那些元素把原列表中位于这个范围之内的元素换掉。与 unpacking 形式的赋值不同,这种赋值不要求等号两边所指定的元素必须相同(如果做 unpacking 机制,那么等号左侧的用来接收数值变量个数必须与等号右侧所提供的数值个数一致,例如 a, b = c[:2],参见 第6条)。在原列表中,位于切片范围之前和之后那些元素会予以保留,但是列表的长度可能会有所变化。
例如,在下面的这个例子中,列表长度会变短,因为赋值表达式右侧只提供了 3 个值,但是左侧那个切片却涵盖了 5 个值,列表会比原来少两个元素。
print('Before: ', a)
a[2:7] = [99, 22, 14]
print('After: ', a)
>>>
Before: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
After: ['a', 'b', 99, 22, 14, 'h']
下面这段代码会使列表变长,因为赋值符号右侧的元素数量比左侧切片所涵盖的元素数量还要多。
print('Before: ', a)
a[2:3] = [47, 11]
print('After: ', a)
>>>
Before: ['a', 'b', 99, 22, 14, 'h']
After: ['a', 'b', 47, 11, 22, 14, 'h']
起始位置都留空的切片,如果出现在赋值符号右侧,那么表示给这个列表做副本,这样制造出来的新列表和原列表相同,但身份不同。
b = a[:]
assert b == a and b is not a
把不带起止下标的切片放在赋值符号左边,表示是用右边那个列表的副本把左侧列表的全部内容替换掉(注意,左侧列表依然保持原来的身份,系统不会分配新的列表)。
b = a
print('Before a', a)
print('Before b', b)
a[:] = [101, 102, 103]
assert a is b
print('After a ', a)
print('After b ', b)
>>>
After a [101, 102, 103]
After b [101, 102, 103]