fluent python讀書筆記2—Python的序列型別1

futureshine發表於2018-09-04

Python中的列表物件是我們程式設計開發都會遇到的型別,其提供的豐富的方法極大地簡化了我們處理資料的過程。但是也很可能在實際使用過程中濫用其特性,降低程式碼的美觀性(說的就是for in)和效能。而對於元組和列表的對比,我們的理解也僅限於可變和不可變,更多的對比,還有待我們探索。

內建序列型別的簡單總結

根據存放內容的特性分類:

  • 容器序列(Container sequences):
    • 如 list, tuple, collections.deque,其特點是可以容納不同型別的元素
  • 扁平序列(Flat sequences):
    • 如 str, bytes, bytearray, memoryview, array,array, 只能容納一個型別的元素

容器序列存放的只是相應物件的引用,而物件本身可以是任意型別。扁平序列則實實在在的把每條元素的值存在了其本身所在的記憶體中。也因此,扁平序列更加簡潔,但他們只能存放原始資料型別,如字元,位元組以及數。

根據序列型別是否可變又可以分組為

  • 可變序列:list, bytearray, array.array, collections.deque, memoryview
  • 不可變序列:tuple, str, bytes

fluent python讀書筆記2—Python的序列型別1
繼承關係

上圖並不是真實的繼承關係,但是便於理解可變序列和不可變序列的具體區別。和不可變序列相比,可變序列實現了更多的特殊方法,使得我們可以更靈活的操作其中的元素。

列表解析和生成器表示式

列表解析和可讀性

# 例子2-1,獲取一段字串中每個字元在Unicode編碼下位置的列表
symbols = `$¢£¥€¤`
codes = []
for symbol in symbols:
    codes.append(ord(symbol))
print codes複製程式碼

對以上程式碼無需多做解釋,當然也還有更加優雅的實現方式,這種方式稱為列表解析(list comprehension)

# 例子2-2,獲取一段字串中每個字元在Unicode編碼下位置的列表
symbols = `$¢£¥€¤`
codes = [ord(symbol) for symbol in symbols]
print codes複製程式碼

小建議:

在 python 中,[], {}, () 內所有的換行都會被忽略。所以我們可以寫出很多行的list,而不必使用 “ 換行號。

在 python 2.x 中,列表解析中的變數賦值會被洩漏到周圍的作用域中,這可能會導致一些意想不到的後果。

# python 2.x
x = `my precious`
dummy = [x for x in `ABC`]
print x # `C`複製程式碼

for 語句中的 x ,被洩漏到了全域性作用域中,並且覆蓋了已經存在的變數 x 。這在 python 3 中得到了改進

# python 3
x = `ABC`
dummy = [ord(x) for x in x]
print x # `ABC`
print dummy # [65, 66, 67]複製程式碼

列表解析 VS map filter

列表解析可以在不借助 lambda 表示式的情況下,做到 map 和 filter 能做到的任何事,並且不損失效能。

# 例子2-3,用列表解析和 map/filter 生成列表
symbols = `$¢£¥€¤`
beyond_ascii = [ord(s) for s in symbols if ord(s) > 127]
print beyond_ascii # [162, 163, 165, 8364, 164]
beyond_ascii = list(filter(lambda c: c > 127, map(ord, symbols)))
print beyond_ascii # [162, 163, 165, 8364, 164]複製程式碼

笛卡爾積

fluent python讀書筆記2—Python的序列型別1
笛卡爾積

如圖所示就是笛卡爾積的圖解,而通過列表解析可以非常方便的實現

# 例子2-4,通過列表解析實現笛卡爾積
colors = [`black`, `white`]
sizes = [`S`, `M`, `L`]
tshirts = [(color, size) for color in colors for size in sizes] # 註釋1
print tshirts
for color in colors: # 註釋2
    for size in sizes:
        print((color, size))
tshirts = [(color, size) for size in sizes for color in colors] # 註釋3複製程式碼
  1. 通過列表解析產生了一個元組的列表
  2. 通過傳統的語句產生,和列表解析結果的順序完全一樣
  3. 注意結果的順序和之前的不一樣

生成器表示式

我們可以直接通過列表解析來初始化元組,陣列或者其他的序列型別,但是用生成器更節省空間,因為它通過迭代器協議 (iterator protocol) 一個個地產生 (yield) 元素,而不是生成一整個列表。

生成器的語法和列表解析的語法相同,不同的是生成器表示式由圓括號包裹,而列表解析由方括號包裹。

# 例子2-5,通過生成器表示式建立元組和陣列
symbols = `$¢£¥€¤`
tuple(ord(symbol) for symbol in symbols) # (36, 162, 163, 165, 8364, 164) 註釋1
import array
array.array(`I`, (ord(symbol) for symbol in symbols)) # array(`I`, [36, 162, 163, 165, 8364, 164]) 註釋2複製程式碼
  1. 如果生成器表示式是一個函式呼叫的唯一引數,那麼圓括號可以省略
  2. 陣列的建構函式接收兩個引數,所以生成器表示式兩邊的圓括號不能省略。
# 例子2-6,用生成器表示式做笛卡爾積
colors = [`black`, `white`]
sizes = [`S`, `M`, `L`]
for tshirt in (`%s %s` % (c, s) for c in colors for s in sizes):
    print(tshirt)複製程式碼

例子 2-6 做了一個簡單的笛卡爾積,但是不需要在記憶體中生成包含六種T恤的列表。當資料量很大的時候,這個可以節省很多記憶體。

元組不只是不可變列表

我們都習慣性的認為元組是不可變的列表(至少我是這麼認為的)。但是還有一個不為人知的意義在於,元組可以當做沒有鍵名的記錄。

用元組做記錄

元組可以用來儲存資料,每條資料的位置資訊都是有其意義的。

# 例子2-7,元組做記錄
lax_coordinates = (33.9425, -118.408056)  # 註釋1
city, year, pop, chg, area = (`Tokyo`, 2003, 32450, 0.66, 8014) # 註釋2
traveler_ids = [(`USA`, `31195855`), (`BRA`, `CE342567`), (`ESP`, `XDA205856`)] # 註釋3
for passport in sorted(traveler_ids): # 註釋4
    print(`%s/%s` % passport)  # 註釋5
# 輸出結果
# BRA/CE342567
# ESP/XDA205856
# USA/31195855
for country, _ in traveler_ids: # 註釋6
    print(country)
# 輸出結果
# USA
# BRA
# ESP複製程式碼
  1. 洛杉磯國際機場的經緯度
  2. 東京的資料:依次是名稱,年份,人口(百萬),人口變化率(%),面積(平方公里)
  3. 由國家編號和護照號碼組成的元組列表
  4. 當我們遍歷列表時,passport指的即是每個元組
  5. % 格式符可以正確的識別元組
  6. for 迴圈知道如何將元組中的元素分別取出來,這就是所謂的解包 (unpacking) 。由於我們對元組中的第二條元素不感興趣,所以用 `_` 來代指。

由於列表良好的解包特性,所以可以用其來儲存我們的資料,並且方便的處理

元組解包

最簡單明瞭的元組解包的例子就是平行賦值 (parallel assignment) 。也就是將一個可遍歷物件賦值給多個變數。

lax_coordinates = (33.9425, -118.408056)
latitude, longitude = lax_coordinates # tuple unpacking
print(latitude)
33.9425
print(longitude)
-118.408056複製程式碼

一元組解包的一個很好應用就是,交換變數的值,而不需要使用一個臨時變數

b, a = a, b複製程式碼

另一個例子就是,呼叫函式時,在引數前加星。

divmode(20, 8) # (2,4)
t = (20, 8)
divmode(*t) # (2, 4)
quotient, remainder = divmode(*t)複製程式碼

這樣方便了函式呼叫者,可以直接通過相應變數獲取結果。這可以帶來很多便利,例如開啟一個檔案

import os
_, filename = os.path.split(`/home/luciano/.ssh/idrsa.pub`)
print(filename) # `idrsa.pub`複製程式碼

使用星 (*) 來獲取超出的元素

python 3 中,* 可以直接用在平行賦值中。

a, b, *rest = range(5)
a, b, rest # (0, 1, [2, 3, 4])
a, b, *rest = range(3)
a, b, rest # (0, 1, [2])
a, b, *rest = range(2)
a, b, rest # (0, 1, [])複製程式碼

平行賦值的時候,* 字首的變數還可以放在其他位置,不一定必須得是末尾

a, *body, c, d = range(5)
a, body, c, d # (0, [1, 2], 3, 4)
*head, b, c, d = range(5)
head, b, c, d # ([0, 1], 2, 3, 4)複製程式碼

上面的程式碼只限 python 3 ,python 2 執行會失敗,sad。

巢狀元組的解包

# 例子2-8,解包巢狀元組來獲取經度
metro_areas = [
    (`Tokyo`, `JP`, 36.933, (35.689722, 139.691667)),
    (`Delhi NCR`, `IN`, 21.935, (28.613889, 77.208889)),
    (`Mexico City`, `MX`, 20.142, (19.433333, -99.133333)),
    (`New York-Newark`, `US`, 20.104, (40.808611, -74.020386)),
    (`Sao Paulo`, `BR`, 19.649, (-23.547778, -46.635833)),
] # 註釋1

print(`{:15} | {:^9} | {:^9}`.format(``, `lat.`, `long.`))
fmt = `{:15} | {:9.4f} | {:9.4f}`
for name, cc, pop, (latitude, longitude) in metro_areas: # 註釋2
    if longitude <= 0: # 註釋3
        print(fmt.format(name, latitude, longitude))複製程式碼
  1. 每個元組包含四個域,最後一個域是經緯度資訊
  2. 把最後一個域賦值給一個元組,就可以將對應的經緯度資訊解包出來
  3. 只輸出西半球的資訊

最後的輸出為:

                |   lat.    |   long.  
Mexico City     |   19.4333 |  -99.1333
New York-Newark |   40.8086 |  -74.0204
Sao Paulo       |  -23.5478 |  -46.6358複製程式碼

有鍵名的元組

上一篇部落格有說到 collections.namedtuple ,用這個可以實現有鍵名的元組

# 例子2-9,定義和使用有鍵名的元組型別
from collections import namedtuple
City = namedtuple(`City`, `name country population coordinates`) # 註釋1
tokyo = City(`Tokyo`, `JP`, 36.933, (35.689722, 139.691667)) # 註釋2
print tokyo # City(name=`Tokyo`, country=`JP`, population=36.933, coordinates=(35.689722,
139.691667))
print tokyo.population # 36.933 註釋3
print tokyo.coordinates # (35.689722, 139.691667)
print tokyo[1] # `JP`複製程式碼
  1. 建立一個有鍵名的元組需要兩個引數,類名和一個鍵名列表。鍵名可以通過一個可遍歷的字串列表或者用空格分隔開的字串來給出。
  2. 資料必須按照對應的位置給出,作為比較,元組的建構函式只需要一個可遍歷物件作為引數
  3. 可以通過鍵的名稱,或者鍵所在的位置直接獲取資料

一個有鍵名的元組還有若干方法和屬性。

# 例子2-10,有鍵名元組的屬性和方法
print City._fields # (`name`, `country`, `population`, `coordinates`) 註釋1
LatLong = namedtuple(`LatLong`, `lat long`)
delhi_data = (`Delhi NCR`, `IN`, 21.935, LatLong(28.613889, 77.208889))
delhi = City._make(delhi_data) # 註釋2
print delhi._asdict() # 註釋3
# 輸出
# OrderedDict([(`name`, `Delhi NCR`), (`country`, `IN`), (`population`, 21.935), (`coordinates`, LatLong(lat=28.613889, long=77.208889))])
for key, value in delhi._asdict().items():
    print(key + `:`, value)
# 輸出
# name: Delhi NCR
# country: IN
# population: 21.935
# coordinates: LatLong(lat=28.613889, long=77.208889)複製程式碼
  1. _fields 是鍵名組成的元組
  2. _make() 使我們可以直接通過一個可遍歷物件來建立一個有鍵名的元組
  3. _asdict() 返回一個由這個帶鍵名元組例項產生的 collections.OrderedDict

不可變的列表:元組

對於這個話題,我們已經很熟悉了,下圖直接給出了兩者的不同之處

fluent python讀書筆記2—Python的序列型別1
image
fluent python讀書筆記2—Python的序列型別1
image

原始碼的github地址

原始碼
以上原始碼均來自於 fluent python 一書,僅為方便閱讀之用。如侵刪。

相關文章