这几种数据结构的共性:
- 都是顺序存储
- 顺序访问
- 可迭代对象(可迭代对象可以用len方法获取其长度)
- 通过索引进行元素的访问
- 可以进行切片操作
切片
切片不会对原有的序列做任何修改,切片的语法为:
seq[start:stop]
从索引start
开始,到索引stop
结束,不包含stop
,返回新的序列,不会对原有的对象做任何修改。
几个特性:
-
start
超出索引范围:start = 0
-
stop
超出索引范围:stop = -1
- 负数索引:实际上可转化为:
len(seq) + index
- 当
start >= stop
时,返回空列表
slice的实现:
lst = list(range(0, 10))
def slice(lst, start=0, stop=0):
if start < 0:
start = len(lst) + start
if stop <= 0:
stop = len(lst) + stop
if stop <= start:
return []
if stop > len(lst):
stop = len(lst)
if start < 0:
start = 0
ret = []
for i, v in enumerate(lst):
if i >= start and i < stop:
ret.append(v)
return ret
print(slice(lst, 3, 2))
print(slice(lst, 2, 5))
print(slice(lst, -100, 100))
运行结果为:
: []
: [2, 3, 4]
: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
如果有了步长之后,上面的规则就会发生变化。接下来加入步长的slice实现:
def slice(lst, start=0, stop=0, step=1):
ret = []
if stop < 0:
tmp = start
start = tmp
stop = start
current = start
while current < stop:
try:
ret.append(lst[current])
except IndexError:
pass
current += step
return ret
切片的一些常用操作:
>>> lst = list(range(0, 10))
>>> lst
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> lst[:] # 等效于copy方法
>>> lst[-5:-3] # 支持负数索引
# start大于等于stop时,返回空列表
>>> lst[3:1]
# 列出偶数,步长为2
lst[::2]
[0, 2, 4, 6, 8]
# 列出偶数,步长为2,并倒序输出
lst[::2][::-1]
[8, 6, 4, 2, 0]
# 列出奇数,步长为2,并倒序输出
lst[::-2]
[9, 7, 5, 3, 1]
# 列出偶数,步长为2,并倒序输出
lst[-2::-2]
[8, 6, 4, 2, 0]
索引
如果索引超出范围,将引发IndexError
的异常。修改元素的时候,如果超出索引范围,也同样引发IndexError
异常。
-
index(value)
方法根据value找索引 -
count(value)
方法统计value出现的次数
enumerate的实现:
def enumerate(iterator):
i = 0
for v in iterator:
yield i, v
i += 1
def enumerate(iterator):
ret = []
i = 0
for v in iterator:
ret.append((i, v))
i += 1
return ret
引用
列表批量赋值:
## 当赋值的序列连续时
# 对切片赋值,会替代原来的元素
>>> lst = list(range(0, 10))
>>> lst
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> lst[3:5] = ['x', 'y', 'z']
>>> lst
[0, 1, 2, 'x', 'y', 'z', 5, 6, 7, 8, 9]
>>> lst = list(range(0, 10))
>>> lst[3:5] = ['x']
>>> lst
[0, 1, 2, 'x', 5, 6, 7, 8, 9]
>>> lst = list(range(0, 10))
>>> lst[3:5] = 'x'
>>> lst
[0, 1, 2, 'x', 5, 6, 7, 8, 9]
## 当赋值的序列不连续时
>>> lst = list(range(0, 10))
>>> lst[3:8:2] = ['x', 'y', 'z']
>>> lst
[0, 1, 2, 'x', 4, 'y', 6, 'z', 8, 9]
>>> lst = list(range(0, 10))
>>> lst[3:8:2] = ['x']
ValueError: attempt to assign sequence of size 1 to extended slice of size 3
>>> lst
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
不建议使用以上的方式对切片赋值的操作。
解包/封包
解构与封装可以叫做解包与封包。
- 解构把集合里的元素复制给变量;
- 封装是用变量构建元组。
解构:按照元素顺序,把线性解构的元素依次赋值给变量。
封装的例子:
t = 1, 2
print(t)
(1, 2)
print(type(t))
<class 'tuple'>
定义一个元组,可以省略小括号。
t1 = (1, 2)
t2 = 1, 2
print(t1 == t2) // t1与t2是等效的
True
封装出来的是元组。封装没有什么难度。解构的变化多样,接下来重点看看解构。
先看一个例子:
In [29]: def swap(a, b):
...: i = a
...: a = b
...: b = i
...: return (a, b)
...:
In [30]: swap(1, 3)
Out[30]: (3, 1)
对上面的代码进行改写,由3行代码,变成了一行代码:
In [31]: def swap(a, b):
...: a, b = b, a
...: return (a, b)
...:
In [32]: swap(1, 3)
Out[32]: (3, 1)
对于如下的代码操作,就是解包:
In [33]: x, y = (1, 3)
In [34]: x
Out[34]: 1
In [35]: y
Out[35]: 3
上面的代码使用的是元组,列表也是可以的:
In [36]: a, b = 1, 3
In [37]: a
Out[37]: 1
In [38]: b
Out[38]: 3
接下来看一下封包:
In [39]: t = 1, 3
In [40]: t
Out[40]: (1, 3)
In [41]: type(t)
Out[41]: tuple
继续看例子:
In [42]: head, tail = list(range(0, 10))
# 将会得到如下的错误,因为=两边的元素数量不一致导致的
ValueError: too many values to unpack (expected 2)
In [43]: head, *tail = list(range(0, 10))
In [44]: head
Out[44]: 0
In [45]: tail
Out[45]: [1, 2, 3, 4, 5, 6, 7, 8, 9]
In [46]: *head, tail = list(range(0, 10))
In [47]: head
Out[47]: [0, 1, 2, 3, 4, 5, 6, 7, 8]
In [48]: tail
Out[48]: 9
如果对一个含有2个元素的列表进行解包:
In [49]: head, *tail = [1, 2]
In [50]: head
Out[50]: 1
In [51]: tail
Out[51]: [2]
如果对一个含有一个元素的列表进行解包:
In [52]: head, *tail = [1]
In [53]: head
Out[53]: 1
In [54]: tail
Out[54]: []
如果对一个空列表进行解包:
In [55]: head, *tail = []
ValueError: not enough values to unpack (expected at least 1, got 0)
针对上述例子的总结:
- 左边不能只有一个星号,还要有其他元素
- 如果左边不用星号,那么左边的元素个数要与右边的元素个数相同
- 左边变量数小于右边元素个数,且左边没有加星号会报错
- 元素按照顺序赋值给变量
- 变量和元素必须匹配
- 加星号变量,可以接收任意个数的元素
- 加星号的变量不能单独出现
针对上述,写一个具体的例子:
def it(lst):
if lst:
head, *tail = lst
print(head)
it(tail)
it(list(range(0, 10)))
0
1
2
3
4
5
6
7
8
9
更复杂一点的例子:
In [63]: head, *tail = [1, 2, 3]
In [64]: head
Out[64]: 1
In [65]: tail
Out[65]: [2, 3]
下面这个例子,在Python2中不能实现:
In [59]: head, *mid, tail = [1, 2, 3, 4, 5]
In [60]: head
Out[60]: 1
In [61]: mid
Out[61]: [2, 3, 4]
In [62]: tail
Out[62]: 5
接下来还有更好玩的,如果我们要丢弃=右边某个值,可以使用下划线来,演示如下:
In [66]: lst = list(range(0, 10))
In [67]: lst
Out[67]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
In [68]: a, b, _, c, *_ = lst
In [69]: a
Out[69]: 0
In [70]: b
Out[70]: 1
In [71]: c
Out[71]: 3
如果我们只想要序列的首位两个元素,可以这样操作:
In [72]: head, *_, tail = lst
In [73]: head
Out[73]: 0
In [74]: tail
Out[74]: 9
再来一发,两边结构要一样:
In [75]: lst = [1, [2, 3], 4]
In [76]: a, (b, c), d = lst
In [77]: a
Out[77]: 1
In [78]: b
Out[78]: 2
In [79]: c
Out[79]: 3
In [80]: d
Out[80]: 4
对上面的例子,再来稍微变化一下,不过两边的结构要一样,解构是支持多层次的。:
In [81]: lst = [1, [2, 3, 4, 5, 6, 8], 9]
In [82]: lst
Out[82]: [1, [2, 3, 4, 5, 6, 8], 9]
In [83]: a, (b, *_, c), d = lst
In [84]: a
Out[84]: 1
In [85]: b
Out[85]: 2
In [86]: c
Out[86]: 8
In [87]: d
Out[87]: 9
注意:
- 解包的时候,两边的结构要一致 (重要的事情说三遍)
- 解包的时候,两边的结构要一致 (重要的事情说三遍)
- 解包的时候,两边的结构要一致 (重要的事情说三遍)
- 只要两边结构一样就行
>>> a, (b, (c, (d,))) = [1, [2, [3, [4]]]]
>>> a
1
>>> b
2
>>> c
3
>>> d
4
python的一个惯例,使用单个下划线表示丢弃该变量。单个下划线也是Python合法的标识符,但是如果不是要丢弃一个变量,通常不要用单个下划线表示一个有意义的变量。
head, *_ = 'I love python'
print(head)
I
key, *_, value = 'env = properties'.partition('=')
print(key)
env
print(value)
properties
非常复杂的数据结构,多层嵌套的线性结构的时候,可以用解构快速提取其中的值。