1. 程式人生 > >第二章 序列構成的陣列

第二章 序列構成的陣列

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 列表推導同filtermap的比較

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的形式返回,我們可以利用它把元組裡的資訊友好的呈現出來