第二章 序列構成的陣列
2.1內建序列型別概覽
Python標準庫用C實現了豐富的序列模型,列舉如下:
1> 序列模型 list、tuple和collection.deque這些序列能存放不同型別的序列
2> 扁平模型 str、bytes、bytearray、memoryview和array.array這類序列只能存放一種型別
容器序列存放的是它們所包含任意型別物件的引用,而扁平序列存放的是值而不是引用。
序列型別還能按照能否被修改來分類:
1> 可變序列 list bytearray array.array collections.deque memoryview
2> 不可變序列 tuple str bytes
下圖顯示了可變序列(MutableSequence)和不可變序列(Sequence)的差異,同時也能看出前者從後者那裡繼承了一些方法。瞭解基類可以幫助我們總結出那些完整的序列型別包含了哪些功能。
2.2 列表推導和生成器表示式
列表推導是構建列表的快捷方式,而生成器表示式則可以建立其他任何型別的序列。( 列表推導(list comprehension)簡稱為listcomps;生成器表示式(generator expression)則稱為genexps )
2.2.1 列表推導和可讀性
通常原則:只用列表推導建立新的列表,並且儘量保持簡短。如果列表推導的程式碼超過了兩行就要考慮是不是得用for迴圈重寫。Python會忽略程式碼裡[]、{}、() 中的換行,故在多行的列表、列表推導、生成器表示式、字典這一類的,可以省略不太好看的續行符 \ 。
1 symbols = 'hseksdth' 2 codes = [] 3 """ 4 ord() 函式是 chr() 函式(對於8位的ASCII字串)或 unichr() 函式(對於Unicode物件)的配對函式,它以一個字元(長度為1的字串)作為引數,返回對應的 ASCII 數值,或者 Unicode 數值,如果所給的 Unicode 字元超出了你的 Python 定義範圍,則會引發一個 TypeError 的異常。5 """ 6 for symbol in symbols: 7 codes.append(ord(symbol)) 8 print(codes) #[104, 115, 101, 107, 115, 100, 116, 104] 9 10 symbols = 'dgshsgjh' 11 codes = [ord(symbol) for symbol in symbols] 12 print(codes) # [100, 103, 115, 104, 115, 103, 106, 104] 13 14 x = 'ABC' 15 dummy = [ord(x) for x in x] 16 print(dummy) # [65, 66, 67]
2.2.2 列表推導同filter和map的比較
Filter和map合起來做的事情,列表推導也可以做,而且還不需要藉助難以理解和閱讀的lambda表示式:
如下示例:
1 symbols = '$%#*&' 2 dummy = [ord(x) for x in symbols] 3 beyound_ascii = [ord(s) for s in symbols if ord(s) > 36] 4 beyound_asciii = list(filter(lambda c:c>36,map(ord,symbols))) 5 print(beyound_ascii) # [37, 42, 38] 6 print(dummy) # [36, 37, 35, 42, 38] 7 print(beyound_asciii) # [37, 42, 38]
2.2.3 笛卡爾積
用列表推導可以生成兩個或以上的可迭代型別的笛卡爾積,笛卡爾積是一個列表,列表裡的元素是由輸入的可迭代型別的元素對構成的元組,因此笛卡爾積的列表長度等於輸入變數的乘積。
如下示例:
1 colors = ['black','white'] 2 sizes = ['S','M','L'] 3 tshirts = [(color,size) for color in colors for size in sizes] 4 print(tshirts) 5 輸出:[('black', 'S'), ('black', 'M'), ('black', 'L'), ('white', 'S'), ('white', 'M'), ('white', 'L')] 6 7 tshirts2 = [(color,size) for size in sizes for color in colors] 8 print(tshirts2) 9 輸出:[('black', 'S'), ('white', 'S'), ('black', 'M'), ('white', 'M'), ('black', 'L'), ('white', 'L')]
列表推導的作用只有一個:生成列表。如果想生成其他型別的序列,生成器表示式就派上了用場。
2.2.3 生成器表示式
雖然也可以用列表推導來初始化元組、陣列或其他序列型別,但是生成器表示式是更好的選擇。這是因為生成器表示式背後遵循了迭代器協議,可以逐個的產生元素,而不是先建立一個完整的列表,然後再把這個列表傳遞到某個建構函式裡。生成器表示式的語法跟列表推到差不多,只不過把方括號換成圓括號。
如下示例:
1 symbols = '*&%$#@' 2 print(tuple(ord(symbol)for symbol in symbols)) 3 輸出:(42, 38, 37, 36, 35, 64) 4 5 print(array.array('I',(ord(symbol)for symbol in symbols))) 6 輸出:array('I', [42, 38, 37, 36, 35, 64]) 7 8 colors = ['R','G','B'] 9 sizes = ['S','M','L'] 10 for tshirt in ('%s %s'%(c,s)for c in colors for s in sizes): 11 print(tshirt) 12 輸出: 13 R S 14 R M 15 R L 16 G S 17 G M 18 G L 19 B S 20 B M 21 B L
【注】1、 如果生成器表示式是一個函式呼叫過程中的唯一引數,那麼不需要額外再用括號把它括起來。
2、array的構造方法需要兩個引數,因此括號是必須的。array構造方法的第一個引數指定了陣列中數字的儲存方式。(後面會講到生成器的工作原理,這裡一筆帶過)
2.3 元組不僅僅是不可變的列表
2.3.1 元組和記錄
元組其實是對資料的記錄:元組中每個元素都存放了記錄中一個欄位的資料,外加這個欄位的位置。正是這個位置資訊給資料賦予了意義。如果只把元組理解為不可變的列表,其它的資訊---它所含有的元素的總數和它們的位置—似乎變得可有可無。但如果把元組當做一些欄位的集合,那麼數量和位置資訊就變得非常重要了。
如下示例:
1 lax_coordinates = (55.236,864.1259) 2 city,year,pop,chg,area = ('Tokyo',2019,1547,0.152,8746) 3 traveler_ids = [('USA','1857642'),('BRA','845696'),('BHY','45963')] 4 for passport in sorted(traveler_ids): 5 print('%s/%s'%passport) 6 輸出: 7 BHY/45963 8 BRA/845696 9 USA/1857642 10 11 for country,_ in traveler_ids: 12 print(country) 13 輸出: 14 USA 15 BRA 16 BHY
【注】1、在迭代過程中,passport變數被繫結到每個元組上。
2、%格式運算子能被匹配到對應元組元素上。
3、for 迴圈可以分別提取元組裡的元素,也叫作拆包(unpacking)。因為元組中第二個元素對我們沒什麼用,所以將它賦給 ‘_’ 佔位符。
2.3.2 元組拆包
元組拆包可以應用到任何可迭代的物件上,唯一的硬性要求是:被可迭代物件中元素數量必須跟接受這些元素的元組空擋數一致,除非我們用 * 號來表示忽略多餘的元素。
最好辨認的元組拆包形式就是平行賦值,也就是說把一個可迭代物件裡的元素,一併賦值到由對應的變數組成的元組中。
1 lax_corrdinates = (33.1258,-56.4895) 2 latitude,longtitude = lax_corrdinates 3 print(latitude) 4 print(longtitude) 5 輸出:33.1258 6 -56.4895
還可以用 * 運算把一個可迭代運算拆開作為函式的引數。
1 b,a = a,b # 這種寫法可以在不使用中間變數的情況下交換 a,b 的值 2 print(divmod(20,8)) # divmod 把除數和餘數運算結合起來,返回包含商和餘數的元組 3 t = (20,8) 4 print(divmod(*t)) 5 quotient,remainder = divmod(*t) 6 print(quotient,remainder) 7 輸出:(2, 4) 8 (2, 4) 9 2 4
下面一個示例,元組拆包的用法則是讓一個函式可以用元組的形式返回多個值,然後呼叫函式的程式碼就能輕鬆地接受這些返回值。比如os.path.split() 函式就會返回以路徑和最後一個檔名組成的元組(path,last_part):
1 filepath,filename = os.path.split('/home/download/myfile/study.docx') 2 print('filepath = ',filepath) 3 輸出:filepath = /home/download/myfile 4 print('filename = ',filename) 5 輸出:filename = study.docx 6 print('full path = '+filepath+'/'+filename) 7 輸出:full path = /home/download/myfile/study.docx
在進行拆包的時候,我們不總是對元組裡所有的資料都感興趣,_佔位符能幫助處理這種情況,如上所示。
除此之外,元組拆包中使用 * 也可以幫助我們將注意力集中在部分元素上面,如下示例用 * 來處理剩下的元素。在python中,函式用*args來獲取不確定數量的引數算是一種經典寫法。
1 a,b,*rest = range(10) 2 print(a,b,rest) 3 輸出:0 1 [2, 3, 4, 5, 6, 7, 8, 9] 4 a1,b1,*rest1 = range(2) 5 print(a1,b1,rest1) 6 輸出:0 1 []
在平行賦值中,*字首只能用在一個變數名前面,但是這個變數可以出現在賦值表示式的任意位置
1 a,*body,c,d = range(5) 2 print(a,body,c,d) 3 輸出:0 [1, 2] 3 4 4 *head,b,c,d = range(5) 5 print(head,b,c,d) 6 輸出:[0, 1] 2 3 4
2.3.3 巢狀元組拆包
接受表示式的元組可以是巢狀的,例如 (a,b,(c,d)) 。只要這個接受元組的巢狀結構符合表示式本身的巢狀結構,python 就可以做出正確的對應。
如下示例:
1 metro_areas = [ 2 ('Tokyo','JP',50.3654,(12.458,63.45896)), 3 ('ShangHai', 'JSH', 62.3789, (85.34596, 111.25)), 4 ('GuangHou', 'GZ', 50.3654, (23.214, 98.125)), 5 ('Zhengzhou', 'ZH', 50.3654, (658.145, 985.12)), 6 ('LaSha', 'LS', 50.3654, (528.461, 753.159)), 7 ] 8 9 print('{:15}|{:^9}|{:^9}'.format('','lat.','long.')) 10 fmt = '{:15}|{:9.4f}|{:9.4f}' 11 for name,cc,pop,(latitude,longitude) in metro_areas: 12 print(fmt.format(name,latitude,longitude)) 13 輸出: 14 | lat. | long. 15 Tokyo | 12.4580 | 63.4590 16 ShangHai | 85.3460 | 111.2500 17 GuangHou | 23.2140 | 98.1250 18 Zhengzhou | 658.1450 | 985.1200 19 LaSha | 528.4610 | 753.1590
【注】python3之前,元組可以作為形參放在函式宣告中,例如:def fn(a,(b,c),d),然而Python3 不在支援這種形式。
2.3.4 具名元組
collections.namedtuple是一個工廠函式,它用來構建一個帶欄位名的元組和一個有名字的類。
用namedtuple構建的類的例項所消耗的記憶體跟元組是一樣的,因為欄位名都被存在對應的類裡面。這個例項跟普通的物件例項比起來也要小一些,因為Python不會用__dict__來存放這些例項的屬性。
1 City = namedtuple('City','name country population coordinates') 2 tokyo = City('Tokyo','JP',36.4598,(86.56987,84.1259)) 3 print('tokyo = ',tokyo) 4 print('tokyo.population = ',tokyo.population) 5 print('tokyo.coordinates = ',tokyo.coordinates) 6 print('City._fields = ',City._fields) 7 輸出: 8 tokyo = City(name='Tokyo', country='JP', population=36.4598, coordinates=(86.56987, 84.1259)) 9 tokyo.population = 36.4598 10 tokyo.coordinates = (86.56987, 84.1259) 11 City._fields = ('name', 'country', 'population', 'coordinates')
由上可知:
1、建立一個具名元組需要兩個引數:類名/類的各個欄位名。後者可以是由數個字串組成的可迭代的物件,或者是由空格分隔開的欄位名組成的字串。
2、存放在對應欄位裡的資料要以一串引數的形式傳入到建構函式中(注意:元組的建構函式只接受單一的可迭代物件)
3、可通過欄位名或者位置來獲取一個欄位的資訊
具名元組也有自己的屬性,列舉幾個最有用的:_fields類屬性、類方法_make(iterable)和例項方法_asdict()
1 Latlong = namedtuple('Latlong','lat long') 2 delhi_data = ('Delhi NCR','IN',21.459,Latlong(25.465,87.1276)) 3 delhi = City._make(delhi_data) 4 print('delhi._asdict() = ',delhi._asdict()) 5 輸出:delhi._asdict() = OrderedDict([('name', 'Delhi NCR'), ('country', 'IN'), ('population', 21.459), ('coordinates', Latlong(lat=25.465, long=87.1276))])
由上可知:
1、_fields屬性是一個包含這個類所有欄位名稱的元組
2、用_make()通過接受一個可迭代物件來生成這個類的一個例項,它的作用跟City(*delhi_data) 是一樣的
3、_asdict() 把具名元組以collections.OrderedDict的形式返回,我們可以利用它把元組裡的資訊友好的呈現出來