30.2.1. 学会对序列做切片

Python有这样一种写法,可以从序列里面切割(slice)出一部分内容,让我们能够轻松地获取原序列的某个子集合。最简单的用法就是切割内置的list、str与bytes。其实,凡是实现了__getitem__与__setitem__这两个特殊方法的类都可以切割。

切割最基本的写法是somelist[start:end],从start开始取,不包括end

In [1]: a = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']

In [2]: print("middle tow: ",a[3:5])
middle tow:  ['d', 'e']

In [3]: print("all but ens",a[1:7])
all but ens ['b', 'c', 'd', 'e', 'f', 'g']

如果从头开始切割列表,可以省略冒号左侧的下标0。

assert a[:5] == a[0:5]

如果一直取到列表末尾,那就应该省略冒号右侧的下标。

assert a[2:] == a[2:len(a)]

用负数做下标表示从列表末尾往前算。下面看一些切割示例:

In [8]: print(a[:])
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']

In [9]: print(a[:5])
['a', 'b', 'c', 'd', 'e']

In [10]: print(a[:-1])
['a', 'b', 'c', 'd', 'e', 'f', 'g']

In [11]: print(a[4:])
['e', 'f', 'g', 'h']

In [12]: print(a[-3:])
['f', 'g', 'h']

In [13]: print(a[2:5])
['c', 'd', 'e']

In [14]: print(a[2:-1])
['c', 'd', 'e', 'f', 'g']

In [15]: print(a[-3:-1])
['f', 'g']

如果起点与终点所确定的范围超出了列表的边界,那么系统会自动忽略不存在的元素

In [18]: first_twenty_items
Out[18]: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']

In [19]: last_twenty_items
Out[19]: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']

切割出来的列表是一份全新的列表。即使把某个元素换掉,也不会影响原列表。

In [20]: 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']

下面这个例子中,列表会变短,因为赋值符号的右侧只提供了3个值,但是左侧那个切片却涵盖了5个值,列表会比原来少两个元素。

In [21]: 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']

而下面这段代码会使列表变长

In [22]: 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']

起止位置都留空的切片,出现在赋值右侧,表示给这个列表做副本。

In [23]: b = a[:]
    ...: assert b == a  and b is not a

把不带起止下标的切片放在赋值符号左边,表示是用右边那个列表的副本把左侧列表的全部内容替换掉。

In [25]: 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)
    ...:
    ...:
Before a ['a', 'b', 47, 11, 22, 14, 'h']
Before b ['a', 'b', 47, 11, 22, 14, 'h']
After a  [101, 102, 103]
After  [101, 102, 103]

要点:

切片要尽可能写得简单一些:如果从头开始选取,就省略起始下标0;如果选到序列末尾,就省略终止下标。

切片允许起始下标或终止下标越界,所以很容易就能表达“取开头多少个元素”(例如a[:20])或“取末尾多少个元素”(例如a[-20:0])等含义,而不用担心切片是否真有这么多元素。

把切片放在赋值符号的左侧可以将原列表中这段范围内的元素用赋值符号右侧的元素替换掉,但可能会改变原列表的长度。