Python 元組和分片">Python 元組和分片

分類:IT技術 時間:2017-09-11

這一篇是 《流暢的 python》 讀書筆記。主要介紹元組、分片、序列賦值以及引用了大師 Edsger W.Dijkstra 為什麽序列從0開始計數 的解釋。

元組

在有些python 的介紹中,元組被稱為 不可變列表 ,這其實是不準確的,沒有完全概括元組的特點。元組除了用作不可變列表,還可以用於 沒有字段名的記錄

元組和記錄

元組其實是對數據的記錄:元組中的每個元素都存放了記錄中一個字段的數據,外加這個數據的位置。

如果把元組當作一些字段的集合,數量和位置信息會變得非常重要。比如以下幾條用元組表示的記錄:

>>> lax_coordinates = (33.9425, -118.408056) # 洛杉磯國際機場的經緯度
 # 東京的一些信息:市名、年份、人口、人口變化和面積
 >>> city, year, pop, chg, area = ('Tokyo', 2003, 32450, 0.66, 8014)

以上這兩個元組每個位置都對應一個數據記錄。

元組拆包

>>> city, year, pop, chg, area = ('Tokyo', 2003, 32450, 0.66, 8014)

這個例子中,我們把元組的數據用一條語句分別賦值給 city, year, pop, chg, area,這就是元組拆包的一個具體應用。

元組拆包可以應用到任何可叠代對象上,但是被叠代的對象窄的元素的數量必須跟接受這些元素的元組的空檔數一致。

比如:

>>> lax_coordinates = (33.9425, -118.408056)
>>> latitude, longitude = lax_coordinates
>>> latitude
33.9425
>>> longitude
-118.408056

還可以用 * 運算符把一個可叠代對象拆開作為函數的參數:

>>> divmod(20, 8)
(2, 4)
>>> t = (20, 8)
>>> divmode(*t)
(2, 4)
>>> quotient, remainder = divmode(*t)
>>> quotient, remainder
(2, 4)

在進行拆包是,我們可能對元組的某些值並不感興趣,這時可以用 _ 占位符處理。比如:

>>> divmode(20, 8)
(2, 4)
>>> _, remainder = divmode(20, 8)  # 這裏我們只關心第二個值
>>> remainder
4

在處理函數參數時,我們經常用 *args 來表示不確定數量的參數。在 python3 中,這個概念被擴展到了平行賦值中:

# python 3 代碼示例
>>> a, b, *rest = range(5)
>> a, b, rest
(0, 1, [2, 3, 4])
# * 前綴只能用在一個變量名前,這個變量可以在其他位置
>>> a, *rest, c, d = range(5) 
>> a, rest, c, d
(0, [1, 2], 3, 4)
>>> a, b, *rest = range(2)
>> a, b, rest
(0, 1, [])

元組也支持嵌套拆包,比如:

>>> l = (1, 2, 3, (4, 5))
>>> a, b, c, (d, e) = l
>>> d
4
>>> 5
4

具名元組

元組作為記錄除了位置以外還少一個功能,那就是無法給字段命名, namedtuple 解決了這個問題。

namedtuple 使用方式實例:

>>> from collecitons import namedtuple
>>> city = namedtuple('City', 'name country population coordinates')
>>> tokyo = City('Tokyo', 'JP', 36.933, (35.689722, 139.691667))
>>> tokyo.population  # 可以使用字段名獲取字段信息
36.933
>>> tokyo[1] # 也可以使用位置獲取字段信息
'JP'
>>> City._fields # _fields 屬性是一個包含這個類所有字段名的元組 
('name', 'country', 'population', 'coordinates')
>>> tokyo_data = http://www.tuicool.com/articles/('Tokyo', 'JP', 36.933, (35.689722, 139.691667))
>>> tokyo = City._make(tokyo_data) # _make() 方法接受一個可叠代對象生成這個類的實例,和 City(*tokyo_data) 作用一致
>>>  tokyo._asdict() # _asdict() 把具名元組以 collections.OrderedDict 的形式呈現
OrderedDict([('name', 'Tokyo'), ('country', 'JP'), ('population', 36.933), ('coordinates', (35.689722, 139.691667))])

collections.namedtuple 是一個工廠函數,它可以用來構建一個帶字段名的元組和一個有名字的類。 namedtuple 構建的類的實例鎖消耗的內存和元組是一樣的,因為字段名都被存放在對應的類裏。這個實例和普通的對象實例相比也更小一些,因為 在這個實例中,Python 不需要用 __dict__ 來存放這些實例的屬性

切片

Python 中列表、元組、字符串都支持切片操作。

在切片和區間操作裏不包含區間範圍的最後一個元素是 Python 的風格。這樣做的好處如下: * 當只有最後一個位置信息時,我們可以快速看出切片和區間裏有幾個元素:range(3) 和 mylist[:3] 都只返回三個元素 * 當氣質位置可見時,可以快速計算出切片和區間的長度,用後一個數減去第一個下標(stop-start)即可。 * 這樣還可以讓我們利用任意一個下標來把序列分割成不重復的兩部分,只要寫成 mylist[:x] 和 mylist[x:] 就可以。

切片除了開始和結束的下標之外還可以有第三個參數,比如: s[a:b:c] ,這裏 c 表示取值的間隔,c 還可以為負值,負值意味著反向取值。

>>> s = 'bicycle'
>>> s[::3]
'bye'
>>> s[::-1]
'elcycib'
>>> s[::2]
'eccb'

a:b:c 這種用法只能作為索引或者下標在[] 中返回一個切片對象:slice(a, b, c)。對 seq[start:stop:step] 進行求值的時候,Python 會調用 seq. getitem (slice(start:stop:step)]。

給切片賦值

如果把切片放在賦值語句的左邊,或者把它作為 del 操作的對象,我們就可以對序列進行嫁接、切除或修改操作,比如:

>>> l = list(range(10))
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l[2:5] = [20, 30]
>>> l
[0, 1, 20, 30, 5, 6, 7, 8, 9]
>>> del l[5:7]
[0, 1, 20, 30, 5, 8, 9]
>>> l[3::2] = [11, 22]
>>> l
[0, 1, 20, 11, 5, 22, 9]
>>> l[2:5] = 100
Traceback (most recent call last):
      file "<stdin>", line 1 in <moduld>
TypeError: can only assign an iterable

如果賦值的對象是一個切片,那麽賦值語句的右側必須是一個可叠代對象。

給切片命名

如果代碼中已經出現了大量的無法直視的硬編碼切片下標,可以使用給切片命名的方式清理代碼。比如你有一段代碼要從一個記錄字符串中幾個固定位置提取出特定的數據字段 比如文件或類似格式 :

### 01234567890123456789012345678901234567890123456789012345678901234
record = '............100....513.25........'
cost = int(record[20:23]) * float(record[31:37])
# 這時,可以先給切片命名,以避免大量無法理解的硬編碼下標,使代碼可讀性更強
SHARES= slice(20, 23)
PRICE = slice(31, 37)
cost = int(record[SHARES]) * float(record[PRICE])

slice() 函數創建了一個 切片對象 ,可以被用在任何切片允許使用的地方,比如:

>>> items = [0, 1, 2, 3, 4, 5, 6]
>>> a = slice(2, 4)
>>> items[2:4]
[2, 3]
>>> items[a]
[2, 3]
>>> items[a] = [10, 11]
>>> items
[0, 1, 10, 11, 4, 5, 6]

如果你有一個切片對象 a,還可以調用 a.start, a.stop, a.step 來獲取更多信息,比如:

>>> a = slice(5, 50, 2)
>>> a.start
5
>>> a.step
2

擴展閱讀 為什麽下標要從0開始

Python 裏的範圍(range)和切片都不會反悔第二個下標所指的元素,計算機科學領域的大師 Edsger W.Dijkstra 在一個很短的備忘錄 Why numbering should start at zero 裏對這一慣例做了說明。以下是部分關鍵說明:

為了表示出自然數的子序列,2, 3, … , 12,不使用省略記號那三個點號,我們可以選擇4種約定方式:

  • a) 2 ≤ i < 13
  • b) 1 < i ≤ 12
  • c) 2 ≤ i ≤ 12
  • d) 1 < i < 13

是否有什麽理由,使選擇其中一種約定比其它約定要好呢?是的,確實有理由。可以觀察到,a) 和 b)有個優點, 上下邊界的相減得到的差,正好等於子序列的長度 。另外,作為推論,下面觀察也成立:在 a),b)中, 假如兩個子序列相鄰的話,其中一個序列的上界,就等於另一個序列的下界 。但上面觀察,並不能讓我們從a), b)兩者中選出更好的一個。讓我們重新開始分析。

一定存在最小的自然數。假如像b)和d)那樣,子序列並不包括下界,那麽當子序列從最小的自然數開始算起的時候,會使得下界進入非自然數的區域。這就比較醜陋了。所以對於下界來說,我們更應該采用≤,正如a)或c)那樣。 現在考慮,假如子序列包括上界,那麽當子序列從最小的自然數開始算起,並且序列為空的時候,上界也會進入非自然數的區域。這也是醜陋的。所以,對於上界,我們更應該采用 <, 正如a)或b)那樣。因此我們得出結論,約定a)是更好的選擇。

  • 比如要表示 0, 1, 2, 3 如果用 b) d) 的方式,下界就要表示成 -1 < i
  • 如果一個空序列用 c) 其實是無法表示的,用 a) 則可以表示成 0 ≤ i < 0

總結

這一篇主要介紹元組、分片、序列賦值以及對 為什麽序列從0開始計數 做了摘錄。

參考鏈接

  • Why numbering should start at zero
  • Why numbering should start at zero: http://www.cs.utexas.edu/users/EWD/ewd08xx/EWD831.PDF

最後,感謝女朋友支持。

歡迎關註(April_Louisa) 請我喝芬達
Tags: gt 分片 記錄 叠代 數據 字段

文章來源:


ads
ads

相關文章
ads

相關文章

ad