为什么切片和区间会忽略最后一个元素
- 当只有最后一个位置信息时,可以快速看出切片和区间里有几个元素:range(3)和my_list[:3]都返回三个元素
- 当起止位置信息都可见时,我们可以快速计算出切片和区间长度,用最后一个数减去第一个下标(stop-start)即可
- 方便用任意一个下标来把序列分割成不重叠的两部分,只要写成my_list[:x]和my_list[x:]就可以
对对象进行切片
s=‘bicycle‘
s[::3]#可以用s[a:b:c]的形式对s在a和b之间以c为间隔取值,c的值还可以负,负值意味着反向取值
‘bye‘
s[::-1]
‘elcycib‘
s[::-2]
‘eccb‘
多维切片和省略
省略的正确书写方法是三个英语句号(...),而不是Unicode码位U+206表示的半个省略号。省略在Python解释器眼中是一个符号。它可以当作切片规范的
一部分,也可以用在函数的参数清单中,比如f(a,...,z),或a[i:...]。在NumPy中,...用作多维数组切片的快捷方式。如果x是四维数组,那么x[i,...]
就是x[i,:,:,:]的缩写
给切片赋值
l=list(range(10))
l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
l[2:5]=[20,30]
l
[0, 1, 20, 30, 5, 6, 7, 8, 9]
del l[5:7]
l
[0, 1, 20, 30, 5, 8, 9]
l[3::2]=[11,22]
l
[0, 1, 20, 11, 5, 22, 9]
l[2:5]=100# 如果赋值的对象是一个切片,那么赋值语句的右侧必须是个可迭代对象,即使只有单独一个值,也要把它转换成可迭代的序列
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_21048/4064895235.py in <module>
----> 1 l[2:5]=100# 如果赋值的对象是一个切片,那么赋值语句的右侧必须是个可迭代对象,即使只有单独一个值,也要把它转换成可迭代的序列
TypeError: can only assign an iterable
l[2:5]=[100]
l
[0, 1, 100, 22, 9]
对序列使用+和*
l=[1,2,3]
l*5
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
5*‘abcd‘
‘abcdabcdabcdabcdabcd‘
+和*都遵循这个规律,不修改原有的操作对象,而是构建一个全新的序列
建立由列表组成的列表
board=[[‘_‘]*3 for i in range(3) ]# 建立一个包含3个列表的列表,被包含的3个列表各自有3个元素
board
[[‘_‘, ‘_‘, ‘_‘], [‘_‘, ‘_‘, ‘_‘], [‘_‘, ‘_‘, ‘_‘]]
board[1][2]=‘X‘
board # 把第1行第2列的元素标记为X
[[‘_‘, ‘_‘, ‘_‘], [‘_‘, ‘_‘, ‘X‘], [‘_‘, ‘_‘, ‘_‘]]
含有3个指向同一对象的引用的列表是毫无用处的
weird_board=[[‘_‘]*3]*3 # 外面的列表其实包含3个指向同一列表的引用
weird_board
[[‘_‘, ‘_‘, ‘_‘], [‘_‘, ‘_‘, ‘_‘], [‘_‘, ‘_‘, ‘_‘]]
weird_board[1][2]=‘0‘
weird_board # 当试图修改第1行第2列的元素时,就立马暴露了列表内的3个引用指向同一个对象的事实
[[‘_‘, ‘_‘, ‘0‘], [‘_‘, ‘_‘, ‘0‘], [‘_‘, ‘_‘, ‘0‘]]
前面犯的错误本质上跟下面的代码犯的错误一样:
row=[‘_‘]*3
board=[]
for i in range(3):
board.append(row)
print(board)
board[2][0]=‘X‘
print(board)
# 追加同一个行对象(row)3次到游戏板(board)
[[‘_‘, ‘_‘, ‘_‘], [‘_‘, ‘_‘, ‘_‘], [‘_‘, ‘_‘, ‘_‘]]
[[‘X‘, ‘_‘, ‘_‘], [‘X‘, ‘_‘, ‘_‘], [‘X‘, ‘_‘, ‘_‘]]
正确的方法相当于下面的代码:
board=[]
for i in range(3):
row=[‘_‘]*3
board.append(row)
print(board)
board[2][0]=‘X‘
print(board)
# 每次迭代中都新建了一个列表,作为新的一行(row)被追加到游戏板(board)
# 正如所期待的,只有第2行第1列的元素被修改
[[‘_‘, ‘_‘, ‘_‘], [‘_‘, ‘_‘, ‘_‘], [‘_‘, ‘_‘, ‘_‘]]
[[‘_‘, ‘_‘, ‘_‘], [‘_‘, ‘_‘, ‘_‘], [‘X‘, ‘_‘, ‘_‘]]
序列的增量赋值
l=[1,2,3]
id(l)# 刚开始时列表的ID
1804782764928
l*=2
l
[1, 2, 3, 1, 2, 3]
id(l)# 运用增量算法后,列表的ID没变,新元素追加到列表上
1804782764928
t=(1,2,3)
id(t)# 元组最开始的ID
1804782776960
t*=2
id(t)# 运用增量算法后新的元组被创建
1804781549888
一个关于+=的谜题
下面的代码运行会发生下面四种情况中的哪一种?
+ t变成(1,2,[30,40])。
+ 因为tuple类型不支持对它的元素赋值,所以会抛出TypeError异常。
+ 以上两个都是
+ 第一个和第二个是对的
t=(1,2,[30,40])
t[2]+=[50,60]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
~\AppData\Local\Temp/ipykernel_21048/3251923064.py in <module>
1 t=(1,2,[30,40])
----> 2 t[2]+=[50,60]
TypeError: ‘tuple‘ object does not support item assignment
t #
(1, 2, [30, 40, 50, 60])
import dis
dis.dis(‘s[a]+=b‘) #s[a]+=b后面的字节码
1 0 LOAD_NAME 0 (s)
2 LOAD_NAME 1 (a)
4 DUP_TOP_TWO
6 BINARY_SUBSCR
8 LOAD_NAME 2 (b)
10 INPLACE_ADD
12 ROT_THREE
14 STORE_SUBSCR
16 LOAD_CONST 0 (None)
18 RETURN_VALUE
6 将s[a]的值存入TOS(Top Of Stack,栈的顶端)
11 计算TOS+=b。这一步能够完成,因为TOS指向的一个可变对象
14 s[a]=TOS赋值。这一步失败,因为s是不可变的元组
list.sort方法和内置函数sorted
fruits=[‘grape‘,‘respberry‘,‘apple‘,‘banana‘]
sorted(fruits) # 新建了一个按照字母排序的字符串列表
[‘apple‘, ‘banana‘, ‘grape‘, ‘respberry‘]
fruits # 原列表并没有变化
[‘grape‘, ‘respberry‘, ‘apple‘, ‘banana‘]
sorted(fruits,reverse=True) # 按照字母降序排序
[‘respberry‘, ‘grape‘, ‘banana‘, ‘apple‘]
sorted(fruits,key=len)
# 新建一个按照长度排序的字符串列表,因为这个排序算法是稳定的,grape和apple的长度都是5,它们的相对位置跟在原列表中是一样的
[‘grape‘, ‘apple‘, ‘banana‘, ‘respberry‘]
sorted(fruits,key=len,reverse=True)
# 按照长度降序排序的结果。结果并不是上面那个结果的完全反转,因为用到的排序算法是稳定的,在长度一样时,grape和apple的相对位置不会发生改变
[‘respberry‘, ‘banana‘, ‘grape‘, ‘apple‘]
fruits # 直到这一步,原列表fruits都没有任何变化
[‘grape‘, ‘respberry‘, ‘apple‘, ‘banana‘]
fruits.sort() # 对原列表就地排序,返回值None会被控制台忽略
fruits # 此时fruits本身被排序
[‘apple‘, ‘banana‘, ‘grape‘, ‘respberry‘]
用bisect来管理已排序的序列
import bisect
import sys
HAYSTACK = [1, 4, 5, 6, 8, 12, 15, 20, 21, 23, 23, 26, 29, 30]
NEEDLES = [0, 1, 2, 5, 8, 10, 22, 23, 29, 30, 31]
ROW_FMT = ‘{0:2d} @ {1:2d} {2}{0:<2d}‘
def demo(bisect_fn):
for needle in reversed(NEEDLES):
position = bisect_fn(HAYSTACK, needle) # 用特定的bisect函数来计算元素应该出现的位置
offset = position * ‘ |‘ # 利用该位置来算出需要几个分隔符号
print(ROW_FMT.format(needle, position, offset)) # 把元素和其应该出现的位置打印出来
if __name__ == ‘__main__‘:
if sys.argv[-1] == ‘left‘: # 根据命令上最后一个参数来选用bisect函数
bisect_fn = bisect.bisect_left
else:
bisect_fn = bisect.bisect
print(‘DEMO:‘, bisect_fn.__name__) # 把选定的函数在抬头打印出来
print(‘haystack ->‘, ‘ ‘.join(‘%2d‘ % n for n in HAYSTACK))
print(bisect_fn)
demo(bisect_fn)
DEMO: bisect_right
haystack -> 1 4 5 6 8 12 15 20 21 23 23 26 29 30
<built-in function bisect_right>
31 @ 14 | | | | | | | | | | | | | |31
30 @ 14 | | | | | | | | | | | | | |30
29 @ 13 | | | | | | | | | | | | |29
23 @ 11 | | | | | | | | | | |23
22 @ 9 | | | | | | | | |22
10 @ 5 | | | | |10
8 @ 5 | | | | |8
5 @ 3 | | |5
2 @ 1 |2
1 @ 1 |1
0 @ 0 0
bisect的表现可以从两个方面来调校
首先可以用它的两个可选参数lo和hi来缩小搜寻的范围。lo的默认值是0,hi的默认值是序列的长度,即len()作用于该序列的返回值
其次,bisect函数其实是bisect_right函数的别名,后面还有个姊妹函数叫bisect_left。它们的区别在于,bisect_left返回的插入位置是原序列中
插入元素相等的位置的元素,也就是新元素会被放置于它相等的元素前面,而bisect_right返回的则是跟它相等的元素之后的位置。
根据一个分数,找到它所对应的成绩
def grade(score, breakpoints=[60, 70, 80, 90], grades=‘FDCBA‘):
i = bisect.bisect(breakpoints, score)
return grades[i]
[grade(score) for score in [33, 99, 77, 70, 89, 90, 100]]
# 利用bisect的函数可以在很长的有序序列中作为index的替代,用来更快速地查找一个元素的位置
[‘F‘, ‘A‘, ‘C‘, ‘C‘, ‘B‘, ‘A‘, ‘A‘]
利用bisect.insort插入新元素
insort(seq, item)把变量item插入到序列seq中,并能保持seq的升序顺序
import random
import bisect
SIZE = 7
random.seed(1729)
my_list = []
for i in range(SIZE):
new_item = random.randrange(SIZE * 2)
bisect.insort(my_list, new_item)
print(‘%2d ->‘ % new_item, my_list)
10 -> [10]
0 -> [0, 10]
6 -> [0, 6, 10]
8 -> [0, 6, 8, 10]
7 -> [0, 6, 7, 8, 10]
2 -> [0, 2, 6, 7, 8, 10]
10 -> [0, 2, 6, 7, 8, 10, 10]