Python的序列類型非常豐富,包括了列表(list),元組(tuple),字符串(str), 字節數組(bytes),隊列(deque),今天我們就着重了解下python中的這些內置序列類型。
內置序列類型介紹
python標准庫中的序列類型使用C語言實現,大體上可分為下面幾類。
容器序列
list、tuple、collections.deque等,能存放不同類型的數據
扁平序列
str、bytes、bytearray、memoryview(內存視圖)、array.array等,存放的是相同類型的數據
容器序列和扁平序列有什么不同呢?
容器序列存放的實際上是對象的引用,因此可以存放不同類型的數據;扁平序列存放的是對象的值,是一段連續的內存空間,因此要求對象必須是相同類型的數據才行,如字符、數字、字節。
當然,序列類型也可以按照能否被修改來分類。
可變序列
list、bytearray、array.array、collections.deque、memoryview。
不可變序列
tuple、str、bytes。
所有的序列類型中最重要也最基礎的就是列表了。列表可以存放各種不同類型的數據。作為已經對python有所了解的讀者,對列表應該很熟悉了。所以,我們直接開始討論列表推導吧。
列表推導,洋文名list comprehension,是一種構造列表的方法,異常強大而且非常常用,但是卻很簡單。掌握列表推導后,還能幫助我們理解生成器的概念。讓我們直接開始吧!
列表推導和生成器表達式
列表推導和可讀性
先來看一個例子:
1 symbols = '¥#%&£γ' 2 codes = [] 3 for symbol in symbols: 4 codes.append(ord(symbol)) 5 print(codes)
以上代碼將字符串symbols中的每個字符的ascii碼追加到codes中,下面我們使用列表生成式來達成的目的
1 symbols = '¥#%&£γ' 2 codes = [ord(symbol) for symbol in symbols] 3 print(codes)
可以看出,列表推導使用更少的代碼完成了相同的事,並且具有更高的可讀性,這也符合python的哲學理念。事實上,很多使用for循環完成的列表操作都可以使用列表推導,然而,要注意的是避免列表推導被濫用。如果列表推導的代碼太多、太復雜的話,你就得重新考量是不是要用for循環重寫了。
注意:python會忽略代碼[],(),{}中的換行,因此位於[],(),{}中的代碼可以省略掉\,直接換行。
列表推導同filter和map的比較
filter和map是python函數式編程中常用的兩個方法,對於一些簡單的創建表的操作,列表推導比filter和map使用起來更方便。
1 symbols = '¥#%&£γ' 2 codes1 = [ord(s) for s in symbols if ord(s) > 200] 3 codes2 = list(filter(lambda c:c>200, map(ord, symbols)))
撲克牌
撲克牌出去大小王分為2,3,4,5,6,7,8,9,10,J,Q,K,4種花色,黑桃(spades)、紅桃(hearts)、梅花(clubs)、方塊(diamonds)。下面我們使用列表推導的方法完成撲克牌的創建。
1 from collections import namedtuple 2 cards = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A'] 3 kinds = ['spades', 'hearts', 'clubs', 'diamonds'] 4 PokerCard = namedtuple('PokerCard', ('card', 'kind')) 5 PokerCards = [PokerCard(card, kind) for card in cards for kind in kinds] 6 print(PokerCards)
namedtuple是collections模塊提供的具名元組,可以以下面的方式調用
PokerCard[0], PockerCard[1], PockerCard.kind,PockerCard.card
最后總結一下,列表推導的作用只有一個就是生成列表,一定不要為了列表推導而列表推導。
生成器表達式
列表推導可以很方便的用來生成列表,但是假如讓你一下子生成100萬個數字呢?1000萬個呢?這個時候列表就不合適了,該輪到我們的生成器表達式大顯身手了。
生成器表達式遵循了迭代器協議(實現了__iter__和__next__方法),它並不會產生任何的元素,而是以定義了某種生成元素的規則,在你真正需要的時候,它才會逐個的產生元素,跟數學中集合描述法的定義很相似。所以無論你要用生成器表達式生成多少個元素,它占用的實際內存依然很小。
生成器表達式的語法規則和列表推導差不多,只是把方括號[]變成了圓括號()
1 symbols = '$¢£¥€¤' 2 codes = list(ord(s) for s in symbols) 3 print(codes) 4 import array 5 arr = array.array('I', (ord(s) for s in symbols)) 6 print(arr)
若果生成器表達式是函數的唯一參數,可以不用括號;array是array模塊提供的數組,需要指定存儲的數據的類型,I表示無符號整數。
元組
元組常常又被稱為“不可變列表”,的確如此,但除此之后,元組還具有字段記錄的功能。這一點常常被人忽略,沒有引起足夠的重視。
元組和記錄
元組也是對數據的記錄,元組中的每個元素都有相應的意義。
元組拆包
可以使用平行賦值的方式對元組進行拆包
#元組拆包 city, population, country = ('beijing', 20000000, 'China') print(city, population, country) #使用*處理不在意的元素 a, b, *rest = range(5) print(a) print(b) print(rest) a, *rest, b = range(5) print(a) print(b) print(rest)
具名元組
還記得上面的撲克牌的例子嗎?有的時候,我們還想要對元組里的字段進行命名,而collections.namedtuple恰恰就是為此而生的。
那么如何使用具名元組呢?
1 City = namedtuple('City', ('name', 'country', 'population', 'location')) 2 beijing = City('beijing', 'China', 1300000000, (35, 120)) 3 print(beijing) 4 print(beijing.name, beijing.country, beijing[3])
namedtuple接收兩個參數,一個是元組名,一個是各個字段的名稱構成的集合,可以是元組也可以是由空格分開的字符串,具名元組使用下標或者成員訪問符.的方式訪問每個字段。
切片
列表、元組、字符串都支持切片操作。
切片為什么要忽略最后一個元素
python的絕大部分包含區間范圍的方法中都是默認忽略最后一個元素的,例如range(10)會忽略最后的元素10,在其他編程語言中也如此。這樣做的好處也是顯而易見的。
1、切片只包含最后一個元素時,很方便的就可以知道元素的數量,如mylist[:3]有3個元素
2、切片包含start和end兩個時,元素數量就是end-start
3、可以很方便的把序列分成互不重疊的兩部分 list[:x], list[x:]
對對象進行切片操作
1 s = 'bicycle' 2 print(s[:3])#'bic' 3 print(s[::-1])#反向取值 elcycib 4 print(s[::-2])#反向取值,步長為2 eccb
list.sort方法和內置sorted
list.sort會把列表就地排序,不會產生新的列表,因此list.sort返回None,這是python的慣例,如果一個方法對對象進行了就地改動,那他就應該返回None。
sorted是內置的排序函數,他會返回一個新的列表。
兩個函數都有兩個關鍵字參數。
reverse
為True時,元素從大到小排序,默認False
key
一個接受一個參數的函數,返回的結果會被用來作為比較的關鍵字進行排序
key=str.lower可以實現忽略大小寫, key=str.len以長度作為排序
1 fruits = ['grape', 'apple', 'banana', 'pear', 'cheery'] 2 print(sorted(fruits)) #默認以字典序排序 3 print(sorted(fruits, reverse=True)) #逆序輸出 4 print(sorted(fruits, key=str.__len__)) #以字符串的長度為關鍵字進行排序 5 print(fruits.sort()) #None, fruits原地排序 6 print(fruits ) #已排序

其他序列類型
列表既靈活又簡單,但在某些需求時,我們可能會有更好的選擇。比如要存放1000萬個浮點數的話,數組(array)的效率就要高得多,再比如如果需要對序列進行頻繁的先進先出的操作,雙端隊列(deque)無疑是更好的選擇。
數組
數組可以看成是只允許存放固定類型數據的高效化列表,數組支持所有list的方法,如pop, insert, extend, insert。此外數組還提供從文件讀取和存入文件更快的方法,frombytes, tofile。
數組的創建array需要兩個參數,一個是數組的類型類型碼,另一個是個列表或者是一個可迭代對象。
類型碼如下:
類型碼 類型 字節
'b' 整型 1
'B' 無符號整型 1
'u' unicode字符 2
'h' 整型 2
'H' 無符號整型 2
'i' 整型 2
'I' 無符號整型 2
'l' 整型 4
'L' 無符號整型 4
'f' 浮點型 4
'd' 浮點型 8
1 from random import random 2 from array import array 3 4 #創建一個包含10000個8字節浮點數的數組 5 floats = array('d', (random() for _ in range(10**4))) 6 print(floats[0], floats[-1], len(floats)) 7 8 #必須要以二進制的方式讀寫文件 9 with open("floats.txt", "wb") as fp: 10 floats.tofile(fp) 11 12 #創建一個空數組 13 floats2 = array('d') 14 with open("floats.txt", "rb") as fp: 15 floats2.fromfile(fp, 10**4) #第二個參數可以知道讀取多少個元素 16 #floats2.frombytes(fp.read()) 與上面是等價的 17 print(floats2[0], floats2[-1], len(floats2)) 18 print(floats==floats2)

除數組外,pickle是另一個能夠序列化數字類型的方法,pickle幾乎可以處理所有的內置類型甚至包括用戶自定義的類。
內存視圖
內存視圖(memoryview)實際上跟C語言中的類型轉換類似,memoryview.cast它引用一塊內存並且可以以指定的方式打包成一個全新的對象返回給你。
1 #創建一個16位二進制數組 2 nums = array('h', [-2,-1,0,1,2]) 3 #該數組的內存視圖 4 memv = memoryview(nums) 5 print(memv[0], len(memv)) 6 7 #該內存視圖以無符號8位二進制方式呈現 8 memv_oct = memv.cast('B') 9 print(list(memv_oct)) 10 11 #等價於把nums[2]的高4位變成了4,所以nums[2]成了1024 12 memv_oct[5] = 4 13 print(nums)

numpy和scipy簡單介紹
相信你即便沒有使用過numpy和scipy這兩個模塊,也一定聽說過它們的大名。目前流行的大數據分析、人工智能很大程度上都依賴於這兩個模塊,這也得益於numpy和scipy提供的性能優越的高階數組和矩陣操作。如果要詳細介紹它們,那不寫幾本書是不可能,所以這里只是簡單介紹下它們對多維數組的基本操作,作為對python基本序列類型的擴展和補充。
1 import numpy 2 a = numpy.arange(12) 3 #print(a) 4 a.shape=(3,4) 5 print(a) 6 #print(a[2]) 7 #print(a[:, -1]) 8 #print(a[:,::2]) 9 print(a.transpose())
雙端隊列和其他形式的隊列
使用列表的append和pop方法我們也可以模擬出隊列“先進先出”的特性,但是列表的刪除第一個元素或者添加第一個元素是很耗時的操作,因為牽扯到后面數據的移動。
collections.deque雙端隊列可以快速的在兩頭添加、刪除元素,append和popleft、appendleft和pop都是原子操作,意味着collections.deque是線程安全的,無需考慮鎖的問題。
1 from collections import deque 2 dq = deque(range(10), maxlen=10) 3 print(dq) 4 5 #旋轉操作 6 dq.rotate(3) 7 print(dq) 8 9 #隊尾追加元素 10 dq.append(11) 11 print(dq) 12 13 #移除隊頭的元素 14 print(dq.popleft()) 15 print(dq)

值得注意的是,deque有一個maxlen參數表明隊列可以容納的元素數量,在隊列已滿的情況下在向隊列里添加元素的時候,則隊頭或者隊尾的元素會被移除。
此外python還有其他模塊提供了隊列。
queue
提供了同步(線程安全)類Queue、LifoQueue、PriorityQueue,構造方法中都有一個maxsize可選參數表示隊列的容量,與collections.deque不同的是,當隊列滿時,隊列會被鎖住,直到有其他線程取出了隊列中的元素為止。
multiprocessing
實現了自己的Queue,與queue.Queue類似,是用來設計給進程間通信使用。
asyncio
異步編程提供的Queue、LifoQueue、PriorityQueue和JoinableQueue。
heapq
heapq沒有隊列類,但是提供了heappush和heappop方法。
