Skip to content

Latest commit

 

History

History
245 lines (178 loc) · 5.59 KB

basic7.md

File metadata and controls

245 lines (178 loc) · 5.59 KB

Python的基础故事(七)——高级切片

本文为大家介绍Python中的切片对象。

切片

切片操作我们都比较熟悉了,可以通过切片获得一个容器的一系列对象,它的基本使用方式是[start:stop:step]

a = [1, 2, 3, 4, 5]
print(a[:2])
[1, 2]

print(a[3:])
[4, 5]

print(a[:4:2])
[1, 3]

print(a[::-1])
[5, 4, 3, 2, 1]

切片中索引与元素的关系可以由下图体现:

  +---+---+---+---+---+---+---+
  | a | b | c | d | e | f | g |   <- 原始数据
  +---+---+---+---+---+---+---+
  | 0 | 1 | 2 | 3 | 4 | 5 | 6 |   <- 正数索引
  +---+---+---+---+---+---+---+
  |-7 |-6 |-5 |-4 |-3 |-2 |-1 |   <- 负数索引
  +---+---+---+---+---+---+---+
  0 : 1 : 2 : 3 : 4 : 5 : 6 : 7   <- 正向正数切片
  +---+---+---+---+---+---+---+
 -7 :-6 :-5 :-4 :-3 :-2 :-1 :None <- 正向负数切片
  +---+---+---+---+---+---+---+
None: 0 : 1 : 2 : 3 : 4 : 5 : 6   <- 反向正数切片
  +---+---+---+---+---+---+---+
 -8 :-7 :-6 :-5 :-4 :-3 :-2 :-1   <- 反向负数切片
  +---+---+---+---+---+---+---+

上图中,正向索引部分任意两个组合获得的结果就是两个数字中间夹着的原始数据,例如,0:-2结果就是[a, b, c, d, e],而-7:7结果就是[a, b, c, d, e, f, g]

a = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
print(a[0:-2])
['a', 'b', 'c', 'd', 'e']

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

print(a[0:None])
['a', 'b', 'c', 'd', 'e', 'f', 'g']

有趣的是,我们可以用超出对象范围的索引值来进行切片,这时并不会抛出IndexError异常:

print(a[0:100])
['a', 'b', 'c', 'd', 'e', 'f', 'g']

print(a[100])
IndexError: list index out of range

反向切片中step参数为负数,在上图中索引值需要从右向左给出,最终结果为反向的数据,例如:

print(a[3:-8:-1])
['d', 'c', 'b', 'a']

print(a[5:None:-1])
['f', 'e', 'd', 'c', 'b', 'a']

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

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

None在切片中的作用是表明“切到结束为止”,如果没有指明起止位置,则默认值为None

Python的切片设计能够让我们更快得确定值的范围。首先,切片指定的范围是一个左闭右开的区间,包含起始值而不包含结束值;此外,结束值减去起始值得到的就是切片出来的长度;第三,a[:x] + a[x:] == a。所以当我们写出a[2:5]时,我们就能确定切出了(5-2=3)个元素,分别是第2,第3和第4个位置的元素。

切片对象

事实上,切片本身也是Python中的一类对象,它的类型是slice。Python中存在内建函数slice(),用以创建切片对象,它接收三个参数,分别是startstopstep,和冒号表达式直接书写是一致的,不同的是只有startstep具有默认值None,所以我们至少需要给出stop的值才能创建切片对象。获得的切片对象可以直接被用以索引元素:

ia = slice(2, 5)
print(a[ia])
['c', 'd', 'e']

slice对象本身仅包含上述三个属性,可以分别访问:

print(ia.start, ia.stop, ia.step)
2 5 None

slice具有唯一一个方法:indices,它接收一个整数length,并将切片对象缩小到start~length~stop这个范围内,,返回一个三元组表示新的起止位置和步长:

i1 = slice(0, 100, 2)
i2 = slice(5, 7, 2)

l = 6

print(i1.indices(l))
(0, 6, 2)

print(i2.indices(l))
(5, 6, 2)

print(a[slice(*i2.indices(l))])
['f']

我们也可以通过__getitem__方法来捕获到slice对象:

class List:
    def __getitem__(self, index):
        print(index)
        
l = List()
l[0]
0

l[:3]
slice(None, 3, None)

l[None:None:None]
slice(None, None, None)

索引元组

如果接触过numpy之类的科学库,会发现它们能够支持高维索引:

import numpy as np
a = np.random.random((4, 4))
print(a[2, 3])
0.1541530854483415

print(a[:2, 3:])
[[0.83999301]
 [0.6960205 ]]

print(a[slice(2), slice(2)])
[[0.37081199 0.80440477]
 [0.76574234 0.40022701]]

内建列表、元组等均没有支持:

a = [[1, 2, 3], [4, 5, 6]]
a[1, 2]
TypeError: list indices must be integers or slices, not tuple

我们依旧利用__getitem__看一下高维索引时发生了什么:

l = List()
l[1, 2]
(1, 2)

l[1, 2, 3, 4]
(1, 2, 3, 4)

l[:2, 3:]
(slice(None, 2, None), slice(3, None, None))

可以看到,高维索引传入的索引参数是元组。我们来尝试为内建容器类型增加简单版本的二维索引(仅支持二维索引):

from collections.abc import *

class List(Sequence):
    def __init__(self, iterable):
        self._data = list(iterable)

    def __len__(self):
        return len(self._data)

    def __getitem__(self, index):
        try:
            data = self._data[index[0]]
            for ind in index[1:]:
                try:
                    data = [_[ind] for _ in data]
                except TypeError:
                    data = data[ind]
            return data
        except TypeError:
            return self._data[index]

m = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

l = List(m)
print(l[:, 2])
[3, 6, 9]

print(l[2, :])
[7, 8, 9]

print(l[:, :])
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

print(l[2:, 1:])
[[8, 9]]

print(l[1])
[4, 5, 6]

numpy的结果对比一下:

a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(a[:, 2])
[3 6 9]

print(a[2, :])
[7 8 9]

print(a[:, :])
[[1 2 3]
 [4 5 6]
 [7 8 9]]

print(a[2:, 1:])
[[8 9]]

print(a[1])
[4 5 6]