爲何程序要從0開始計數

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

元組

在有些python 的介紹中,元組被稱爲不可變列表,這實際上是不許確的,沒有徹底歸納元組的特色。元組除了用做不可變列表,還能夠用於沒有字段名的記錄python

元組和記錄

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

若是把元組看成一些字段的集合,數量和位置信息會變得很是重要。好比如下幾條用元組表示的記錄:函數

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

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

元組拆包

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

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

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

好比:3d

>>> lax_coordinates = (33.9425, -118.408056)
>>> latitude, longitude = lax_coordinates
>>> latitude
33.9425
>>> longitude
-118.408056複製代碼

還能夠用 * 運算符把一個可迭代對象拆開做爲函數的參數:rest

>>> divmod(20, 8)
(2, 4)
>>> t = (20, 8)
>>> divmode(*t)
(2, 4)
>>> quotient, remainder = divmode(*t)
>>> quotient, remainder
(2, 4)複製代碼

在進行拆包是,咱們可能對元組的某些值並不感興趣,這時能夠用 _ 佔位符處理。好比:code

>>> 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 = ('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開始計數作了摘錄。

參考連接


最後,感謝女友支持。

歡迎關注(April_Louisa) 請我喝芬達
歡迎關注
歡迎關注
請我喝芬達
請我喝芬達
相關文章
相關標籤/搜索