第二章–序列構成的數組
2.1 python內置序列類型
- 按存儲類型來分
容器系列 list、tuple、collections.deque 能存放不同類型的數據
扁平系列 str、bytes、bytearray、memoryview、array.array 只能存放特定的一種類型的數據
容器系列存放的是對象的引用,扁平系列存放的是值,是一段連續的內存空間
- 按能否被修改來分
可變序列 list、bytearray、array.array、collections.deque 和 memoryview
不可變序列 tuple、str 和 bytes
Sequence是不可變序列,MutableSequence是可變序列,圖中列舉了兩者的一些方法
2.2 生成列表與生成器表達式
生成列表略 把列表推到的[]改成()就成了生成器表達式,前者返回整個列表,後者返回生成器
2.3 元祖與拆包
-
拆包(unpacking)
- 拆包可以用於任何可迭代對象上,但對字典進行拆包智能拿到key值
- 拆包時要注意變量數目左右要一直
- *arr 可以對arr進行拆包 (命令行下不能用)
- 函數中有一種特殊的拆包fun(a,*args,**kargs) args是元祖,接受多餘的無命名參數,kargs是字典,接受多餘的命名參數
- 可以用*來接收剩下的元素
a,b,*rest=range(5) #0 1 [2, 3, 4]
a,*rest,b,c=range(5)# 0 [1, 2] 3 4
- 兩位數交換可以用 a,b=b,a
- 可以用collections.namedtuple代替元祖
namedtuple的一些使用函數 _fields _make _asdict
from collections import namedtuple
Student=namedtuple('Student',['name','grader'])
stu=Student('jason',2)
stu.name #jason
Student._fields # ('name', 'grader')
stu1=Student._make(('mike',3)) # Student(name='mike', grader=3)
stu1._asdict() # OrderedDict([('name', 'mike'), ('grader', 3)])
2.4 切片
-
多維切片
[]運算符中可以使用逗號分開的多個索引或者切片,numpy就用了這個特性
二維的 numpy.ndarray 就可以用 a[i, j] 這種形式來獲取,抑或是用 a[m:n, k:l]
的方式來得到二維切片
對象的特殊方法 __getitem__ 和__setitem__ 需要以元組的形式來接收 a[i, j] 中的索
引。
python內置的序列類型都是一維的,只支持單一索引 -
給切片賦值,等式右邊必須是一個可迭代對象
arr=[1,2,3,4,5,6]
arr[2:4]=[999]*3
print(arr)
arr[2:4]=[888]
print(arr)
arr[2::3]=[777,666]
print(arr)
arr[2:3]=777 # raise exception
#[1, 2, 999, 999, 999, 5, 6]
# [1, 2, 888, 999, 5, 6]
# [1, 2, 777, 999, 5, 666]
2.5 對序列使用+和*
對序列使用+ 和* 不會修改原來的序列進行修改,會返回新的序列結果
注意嵌套列表的重複引用問題
arr=[[0]*3]*3
arr[0][0]=1
print(arr) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]]
arr=[[0]*3 for i in range(3)]
arr[0][0]=1
print(arr) # [[1, 0, 0], [0, 0, 0], [0, 0, 0]]
2.6 序列的增量賦值
增量賦值運算符 += 和 = 的表現取決於它們的第一個操作對象
+=跟=的概念是一樣的,只介紹+=
+= 背後的特殊方法是__iadd__ 如果一個類調用+=時沒有實現iadd,python會調用它的add
ex: a+=b 如果a類沒有實現iadd方法,就會調用add方法
class ClassA():
def __init__(self,num=1):
self.num=num
def __add__(self, other):
return ClassA(self.num+other)
# def __iadd__(self, other):
# self.num+=other
# return self
class ClassB():
def __init__(self,num=1):
self.num=num
def __add__(self, other):
return ClassB(self.num+other)
def __iadd__(self, other):
self.num+=other
return self
#a 只實現了add方法,可以看到調用a+=1前後a的id不一樣,說明調用了add方法
a=ClassA()
print(a.num, id(a))
a+=1
print(a.num, id(a))
# 1 2335603349376
# 2 2335603349488
#b實現了iadd 所以直接用iadd的方法,沒有用add,所以b+=1前後id一致
b=ClassB()
print(b.num,id(b))
b+=1
print(b.num,id(b))
# 1 2335603349432
# 2 2335603349432
對不可變序列進行拼接操作效率會很低 比如對元祖t 執行t*=2 t+=(2,3)不會直接在t內進行修改,而是計算得出結果保存到新變量,再把新變量賦值給t 並沒有就地賦值
但str是一個例外,str是不可變序列,但經常有s+=‘abc’這種操作,所以CPython進行了優化,讓str支持就地賦值,在str初始化內存時留出額外的內存空間,這樣可以一定程度上避免str的移動
一個很神奇的問題
用dis模塊查看了s[a]+=b的字節碼,可以找到原因
從這個問題學到的經驗
- 不要把可變對象放到不可變序列中
- 用dis查看python字節碼對調試非常有幫助
- 不可變序列的增量賦值不是原子操作
list.sort方法和內置函數sorted
python一些對數據進行就地修改的函數會返回None值,提醒你不會返回新的數據,比如list.sort() random.shuttle()
sorted相反,它最終會返回一個排好序的新列表,它接受任何可迭代對象作爲參數,並一直返回新列表
兩個排序函數都有的常用參數有reverse跟key
bisect–二分查找算法
bisect模塊包含兩個主要函數bisect和insort,兩個函數都利用二分查找算法在有序序列中查找插入元素
bisect其實是bisect_right,還有對應的biset_left
bisect_left 返回的插入位置是原序列中跟被插入元素相等的元素的位置,
也就是新元素會被放置於它相等的元素的前面,而 bisect_right 返回的則是跟它相等的元素
之後的位置。這可能會導致不同,因爲兩個元素相等但不一定相同,比如1跟1.0
def grade(score,breakpoints=[60,70,80,90],grade='FDCBA'):
import bisect
i=bisect.bisect(breakpoints,score)
return grade[i]
print(list(map(grade,[33, 99, 77, 70, 89, 90, 100])))#['F', 'A', 'C', 'C', 'B', 'A', 'A']
insort也有對應的insort_left,都實現對有序序列的插入後保持序列有序
###列表的其他替代品
- array模塊
如果我們只需要存儲一種類型的序列化數據,array.array比list更高效,array.array支持所有list相關的操作,並且提供更快的文件讀取方法如.frombytes .tofile
創建array時需要提供類型碼,這個類型碼用於表示底層c語言的數據類型,數據存儲是連續的
import array
arr=array.array('i',[0,1,1,3])
print(arr) # array('i', [0, 1, 1, 3])
print(arr.typecode) # i
print(arr.itemsize) # 4
print(arr.buffer_info()) #(2050462494912, 4) 返回的是數組的開頭地址跟數據個數
print(arr.count(1))#1
arr.pop(3)
print(arr) # array('i', [0, 1, 1])
arr.remove(0)
print(arr) # array('i', [1, 1])
print(array.typecodes) #bBuhHiIlLqQfd
- memoryview
memoryview() 函數返回給定參數的內存查看對象(Momory view)。所謂內存查看對象,是指對支持緩衝區協議的數據進行包裝,在不需要複製對象基礎上允許Python代碼訪問。
memoryview是內置函數,它能讓用戶在不復制內容的情況下操作同一個數組的不同切片
http://stackoverflow.com/questions/4845418/when-should-a-memoryview-be-used/
內存視圖其實是泛化和去數學化的 NumPy 數組。它讓你在不需要複製內容的前提下,
在數據結構之間共享內存。其中數據結構可以是任何形式,比如 PIL 圖片、SQLite
數據庫和 NumPy 的數組,等等。這個功能在處理大型數據集合的時候非常重要。
memoryview.cast能用不同的方式讀寫同一塊內存數據,而且內容字節不會移動
import array
numbers=array.array('h',[-2,-1,0,1,2]) #h表示短整型有符號整數 兩個字節一個數
memv=memoryview(numbers)
print(len(memv)) # 5
print(memv[0])# -2
memv_oct=memv.cast('B') # B表示無符號字符,一個字節
print(memv_oct.tolist()) # [254, 255, 255, 255, 0, 0, 1, 0, 2, 0]
memv_oct[5]=4 # 低字節的在數據前面,高字節在後面,所以index=5的是numbers[2]的高位字節
print(numbers) # array('h', [-2, -1, 1024, 1, 2])
- 隊列
- 雙向隊列 collections.deque 線程安全
- queue模塊 線程安全 提供了Queue、LifoQueue 和 PriorityQueue 三者都有一個參數maxsize,限定隊列的大小,滿員時不會扔掉元素,而是會鎖住當前線程,等待其他線程移除了元素騰出位置
- multiprocessing模塊 實現了用於多進程通信的Queue,還有一個multiprocessing.JoinableQueue 允許項目的使用者發送通知通知隊列的生成者數據已經被處理
用的是task_done函數 - asyncio 主要實現了協程層面的隊列 Queue、LifoQueue、PriorityQueue 和 JoinableQueue
- heapq 提供heappush heappop方法,讓用戶把可變序列當做堆序列或優先隊列來使用