跳轉到內容

Python 程式設計思想/元組

來自華夏公益教科書,自由的教科書

元組是不可變的

[編輯 | 編輯原始碼]

元組是值的序列。這些值可以是任何型別,並且它們由整數索引,因此在這方面元組很像列表。重要的區別是元組是不可變的。

從語法上講,元組是逗號分隔的值列表

>>> t = 'a', 'b', 'c', 'd', 'e'

雖然沒有必要,但通常用括號括住元組

>>> t = ('a', 'b', 'c', 'd', 'e')

要建立一個只有一個元素的元組,您必須包含最後一個逗號

>>> t1 = ('a',)
>>> type(t1)
<type 'tuple'>

如果沒有逗號,Python 會將 ('a') 視為括號中的字串

>>> t2 = ('a')
>>> type(t2)
<type 'str'>

另一種建立元組的方法是使用內建函式 tuple。沒有引數,它將建立一個空元組

>>> t = tuple()
>>> print t
()

如果引數是序列(字串、列表或元組),則結果是包含序列元素的元組

>>> t = tuple('lupins')
>>> print t
('l', 'u', 'p', 'i', 'n', 's')

因為 tuple 是內建函式的名稱,所以您應該避免使用它作為變數名。

大多數列表運算子也適用於元組。方括號運算子對元素進行索引

>>> t = ('a', 'b', 'c', 'd', 'e')
>>> print t[0]
'a'

切片運算子選擇一系列元素。

>>> print t[1:3]
('b', 'c')

但是,如果您嘗試修改元組中的元素之一,則會出現錯誤

>>> t[0] = 'A'
TypeError: object doesn't support item assignment

您不能修改元組的元素,但您可以用另一個元組替換一個元組

>>> t = ('A',) + t[1:]
>>> print t
('A', 'b', 'c', 'd', 'e')

元組賦值

[編輯 | 編輯原始碼]

交換兩個變數的值通常很有用。對於傳統的賦值,您必須使用臨時變數。例如,要交換 ab

>>> temp = a
>>> a = b
>>> b = temp

此解決方案很繁瑣;元組賦值更優雅

>>> a, b = b, a

左側是變數元組;右側是表示式元組。每個值都被分配給各自的變數。右側的所有表示式在任何賦值之前都被計算。

左側的變數數量和右側的值數量必須相同

>>> a, b = 1, 2, 3
ValueError: too many values to unpack

更一般地說,右側可以是任何型別的序列(字串、列表或元組)。例如,要將電子郵件地址拆分為使用者名稱和域名,您可以編寫

>>> addr = 'monty@python.org'
>>> uname, domain = addr.split('@')

split 的返回值是包含兩個元素的列表;第一個元素分配給 uname,第二個分配給 domain

>>> print uname
monty
>>> print domain
python.org

元組作為返回值

[編輯 | 編輯原始碼]

嚴格來說,函式只能返回一個值,但如果該值是元組,則其效果與返回多個值相同。例如,如果您想將兩個整數相除並計算商和餘數,則計算 x/y 然後計算 x%y 很低效。最好同時計算兩者。

內建函式 divmod 接受兩個引數並返回包含兩個值的元組,即商和餘數。您可以將結果儲存為元組

>>> t = divmod(7, 3)
>>> print t
(2, 1)

或者使用元組賦值單獨儲存元素

>>> quot, rem = divmod(7, 3)
>>> print quot
2
>>> print rem
1

這是一個返回元組的函式示例

def min_max(t):
    return min(t), max(t)

maxmin 是內建函式,用於查詢序列中的最大和最小元素。min_max 計算兩者並返回包含兩個值的元組。

可變長度引數元組

[編輯 | 編輯原始碼]

函式可以接受可變數量的引數。以 * 開頭的引數名將引數收集到元組中。例如,printall 接受任意數量的引數並列印它們

def printall(*args):
    print args

收集引數可以是任何你喜歡的名稱,但 args 是約定俗成的。以下是函式的工作原理

>>> printall(1, 2.0, '3')
(1, 2.0, '3')

您可以將收集運算子與必需引數和位置引數組合起來

def pointless(required, optional=0, *args):
    print required, optional, args

使用 1、2、3 和 4 個或更多引數執行此函式,並確保您瞭解它的作用。

收集的補充是散佈。如果您有一系列值並且想將其作為多個引數傳遞給函式,您可以使用 * 運算子。例如,divmod 恰好接受兩個引數;它不接受元組

>>> t = (7, 3)
>>> divmod(t)
TypeError: divmod expected 2 arguments, got 1

但是,如果您散佈元組,它就可以工作

>>> divmod(*t)
(2, 1)

許多內建函式使用可變長度引數元組。例如,'max' 和 'min' 可以接受任意數量的引數

''>>> max(1,2,3)
3
''

但 'sum' 則不行。


''>>> sum(1,2,3)
TypeError: sum expected at most 2 arguments, got 3
''

編寫一個名為 'sumall' 的函式,它接受任意數量的引數並返回它們的總和。

列表和元組

[編輯 | 編輯原始碼]

zip 是一個內建函式,它接受兩個或多個序列並將它們“壓縮”成一個包含元組的列表1,其中每個元組都包含來自每個序列的一個元素。

此示例壓縮字串和列表

>>> s = 'abc'
>>> t = [0, 1, 2]
>>> zip(s, t)
[('a', 0), ('b', 1), ('c', 2)]

結果是一個包含元組的列表,其中每個元組都包含字串中的一個字元和列表中的相應元素。

如果序列的長度不同,則結果的長度與最短序列的長度相同。

>>> zip('Anne', 'Elk')
[('A', 'E'), ('n', 'l'), ('n', 'k')]

您可以在 for 迴圈中使用元組賦值來遍歷元組列表

t = [('a', 0), ('b', 1), ('c', 2)]
for letter, number in t:
    print number, letter

每次迴圈時,Python 都會從列表中選擇下一個元組並將元素分配給 letternumber。此迴圈的輸出為

0 a
1 b
2 c

如果您將 zipfor 和元組賦值組合起來,您將得到一個有用的習語,用於同時遍歷兩個(或多個)序列。例如,has_match 接受兩個序列 t1t2,如果存在一個索引 i 使得 t1[i] == t2[i],則返回 True

def has_match(t1, t2):
    for x, y in zip(t1, t2):
        if x == y:
            return True
    return False

如果您需要遍歷序列的元素及其索引,可以使用內建函式 enumerate

for index, element in enumerate('abc'):
    print index, element

此迴圈的輸出為

0 a
1 b
2 c

同樣。

字典和元組

[編輯 | 編輯原始碼]

字典有一個名為 items 的方法,它返回一個元組列表,其中每個元組都是一個鍵值對2

>>> d = {'a':0, 'b':1, 'c':2}
>>> t = d.items()
>>> print t
[('a', 0), ('c', 2), ('b', 1)]

正如您對字典的期望,這些項沒有特定的順序。

相反,您可以使用元組列表來初始化一個新的字典

>>> t = [('a', 0), ('c', 2), ('b', 1)]
>>> d = dict(t)
>>> print d
{'a': 0, 'c': 2, 'b': 1}

dictzip 組合起來可以提供一種簡潔的方式來建立字典

>>> d = dict(zip('abc', range(3)))
>>> print d
{'a': 0, 'c': 2, 'b': 1}

字典方法 update 也接受一個元組列表並將它們作為鍵值對新增到現有字典中。

items、元組賦值和 for 組合起來,您可以得到遍歷字典鍵和值的習語

for key, val in d.items():
    print val, key

此迴圈的輸出為

0 a
2 c
1 b

同樣。

在字典中使用元組作為鍵是很常見的(主要是因為不能使用列表)。例如,電話簿可以將姓氏和名字的組合對映到電話號碼。假設我們已經定義了lastfirstnumber,我們可以寫成

directory[last,first] = number

方括號中的表示式是一個元組。我們可以使用元組賦值來遍歷這個字典。

for last, first in directory:
    print first, last, directory[last,first]

這個迴圈遍歷了directory中的鍵,這些鍵都是元組。它將每個元組的元素賦值給lastfirst,然後列印名字和相應的電話號碼。

在狀態圖中,可以用兩種方法表示元組。更詳細的方法是顯示索引和元素,就像它們出現在列表中一樣。例如,元組('Cleese', 'John')將顯示為

<IMG SRC="book020.png">

但在更大的圖中,你可能希望省略細節。例如,電話簿的圖可能顯示為

<IMG SRC="book021.png">

這裡使用 Python 語法作為圖形速記來顯示元組。

圖中的電話號碼是 BBC 的投訴熱線,請不要撥打。

比較元組

[edit | edit source]

比較運算子適用於元組和其他序列;Python 從比較每個序列的第一個元素開始。如果它們相等,則繼續比較下一個元素,依此類推,直到找到不同的元素。後面的元素不再考慮(即使它們很大)。

>>> (0, 1, 2) &lt; (0, 3, 4)
True
>>> (0, 1, 2000000) &lt; (0, 3, 4)
True

sort函式的工作方式相同。它主要按第一個元素排序,但在出現相同的情況下,它按第二個元素排序,依此類推。

此特性適用於稱為DSU的模式,用於

裝飾
透過構建一個元組列表,其中一個或多個排序鍵位於序列元素之前,來對序列進行裝飾。
排序
排序元組列表,以及
取消裝飾
透過提取序列的排序元素來取消裝飾。

例如,假設你有一個單詞列表,你想按從長到短的順序排序

def sort_by_length(words):
    t = []
    for word in words:
       t.append((len(word), word))

    t.sort(reverse=True)

    res = []
    for length, word in t:
        res.append(word)
    return res

第一個迴圈構建一個元組列表,其中每個元組都是一個單詞,前面是它的長度。

sort首先比較第一個元素長度,如果相等,則只考慮第二個元素。關鍵字引數reverse=True告訴sort按降序排序。

第二個迴圈遍歷元組列表,並構建一個按長度降序排列的單詞列表。

練習 2

[edit | edit source]

在這個例子中,相同情況會透過比較單詞來解決,因此具有相同長度的單詞會按字母順序排列。對於其他應用程式,你可能希望隨機解決相同情況。修改此示例,以便具有相同長度的單詞以隨機順序排列。提示:檢視'random'模組中的'random'函式。

序列的序列

[edit | edit source]

我重點介紹了元組列表,但這章中的幾乎所有示例也適用於列表列表、元組元組和列表元組。為了避免列舉所有可能的組合,有時更容易討論序列的序列。

在許多情況下,不同型別的序列(字串、列表和元組)可以互換使用。那麼你如何以及為什麼選擇其中之一呢?

首先,字串比其他序列更受限制,因為元素必須是字元。它們也是不可變的。如果你需要更改字串中的字元的能力(而不是建立一個新的字串),你可能希望使用字元列表。

列表比元組更常見,主要是因為它們是可變的。但有一些情況你可能更喜歡元組

  • 在某些情況下,例如return語句,建立元組比建立列表在語法上更簡單。在其他情況下,你可能更喜歡列表。
  • 如果你想使用序列作為字典鍵,則必須使用不可變型別,例如元組或字串。
  • 如果你將序列作為引數傳遞給函式,則使用元組可以減少由於別名導致的意外行為的可能性。

由於元組是不可變的,因此它們不提供像sortreverse這樣的方法,這些方法會修改現有的列表。但是 Python 提供了內建函式sortedreversed,它們接受任何序列作為引數,並返回一個包含相同元素的按不同順序排列的新列表。

除錯

[edit | edit source]

列表、字典和元組通常被稱為資料結構;在本節中,我們開始看到複合資料結構,例如元組列表,以及包含元組作為鍵和列表作為值的字典。複合資料結構很有用,但它們容易出現我稱為形狀錯誤的錯誤;也就是說,由資料結構具有錯誤的型別、大小或組成導致的錯誤。例如,如果你期望一個包含一個整數的列表,而我給你一個普通的整數(不在列表中),它將不起作用。

為了幫助除錯這些型別的錯誤,我編寫了一個名為structshape的模組,它提供了一個名為structshape的函式,該函式接受任何型別的資料結構作為引數,並返回一個總結其形狀的字串。你可以在thinkpython.com/code/structshape.py下載它

以下是簡單列表的結果

>>> from structshape import structshape
>>> t = [1,2,3]
>>> print structshape(t)
list of 3 int

一個更復雜的程式可能會寫“包含 3 個 ints 的列表”,但這更容易避免處理複數。以下是一個列表列表

>>> t2 = [[1,2], [3,4], [5,6]]
>>> print structshape(t2)
list of 3 list of 2 int

如果列表的元素型別不同,structshape會按型別對它們進行分組,並按順序排列

>>> t3 = [1, 2, 3, 4.0, '5', '6', [7], [8], 9]
>>> print structshape(t3)
list of (3 int, float, 2 str, 2 list of int, int)

以下是一個元組列表

>>> s = 'abc'
>>> lt = zip(t, s)
>>> print structshape(lt)
list of 3 tuple of (int, str)

以下是一個包含 3 個項的字典,它們將整數對映到字串。

>>> d = dict(lt) 
>>> print structshape(d)
dict of 3 int->str

如果你難以跟蹤你的資料結構,structshape可以提供幫助。

詞彙表

[edit | edit source]
元組
不可變的元素序列。
元組賦值
在右邊使用序列,在左邊使用元組變數進行的賦值。右側被計算,然後其元素被賦值給左側的變數。
收集
組裝可變長度引數元組的操作。
分散
將序列視為引數列表的操作。
DSU
“裝飾-排序-取消裝飾”的縮寫,這種模式涉及構建一個元組列表、排序和提取結果的一部分。
資料結構
相關值的集合,通常組織在列表、字典、元組等中。
形狀(資料結構的形狀)
對資料結構的型別、大小和組成的總結。

練習

[edit | edit source]

練習 3

[edit | edit source]

編寫一個名為most_frequent的函式,它接收一個字串,並按頻率遞減的順序列印字母。從幾種不同的語言中找到文字樣本,並檢視不同語言之間的字母頻率差異。將你的結果與'wikipedia.org/wiki/Letter_frequencies'中的表格進行比較。

練習 4

[edit | edit source]

更多 anagrams!

  • 編寫一個程式,從檔案(見第 '9.1' 節)讀取單詞列表,並列印所有是 anagrams 的單詞集。

以下是一個可能的輸出示例

''['deltas', 'desalt', 'lasted', 'salted', 'slated', 'staled']
['retainers', 'ternaries']
['generating', 'greatening']
['resmelts', 'smelters', 'termless']
''

提示:你可能希望構建一個字典,將字母集對映到可以用這些字母拼寫的單詞列表。問題是,你如何用可以作為鍵的方式表示字母集?

  • 修改前面的程式,以便它首先列印最大的 anagrams 集,然後列印第二大的集合,依此類推。
  • 在 Scrabble 中,“bingo”是指當你玩了你牌架上的所有七個棋子,以及棋盤上的一個字母,形成一個八個字母的單詞。哪組 8 個字母可以形成最多的可能的 bingo?提示:有七個。
  • '如果你可以透過交換兩個字母來將一個單詞轉換為另一個單詞,那麼這兩個單詞構成一個“metathesis 對”''3'';例如,“converse”和“conserve”。編寫一個程式,找到字典中所有的 metathesis 對。提示:不要測試所有單詞對,也不要測試所有可能的交換。'

'您可以從 '''thinkpython.com/code/anagram_sets.py'''下載解決方案。'

這是另一個 Car Talk 謎題4

最長的英語單詞是什麼,在您逐個刪除字母時仍然是有效的英語單詞?

現在,可以從兩端或中間刪除字母,但不能重新排列任何字母。每次刪除一個字母后,都會得到另一個英語單詞。如果您這樣做,最終會得到一個字母,它也是英語單詞——字典中找到的單詞。我想知道最長的單詞是什麼,以及它有多少個字母?

我將給您一個簡單的例子:Sprite。好的?從 sprite 開始,刪除一個字母,從單詞的內部刪除一個字母,刪除 r,剩下 spite,然後從末尾刪除 e,剩下 spit,刪除 s,剩下 pit,it,和 I。

編寫一個程式來查詢所有可以以這種方式縮減的單詞,然後找到最長的單詞。

這個練習比大多數練習更具挑戰性,所以這裡有一些建議

  • 您可能需要編寫一個函式,該函式接受一個單詞並計算所有可以透過刪除一個字母形成的單詞。這些是單詞的“子節點”。
  • 遞迴地,如果一個單詞的任何子節點是可縮減的,那麼它就是可縮減的。作為基本情況,您可以考慮空字串是可縮減的。
  • 我提供的單詞列表 'words.txt' 不包含單個字母的單詞。因此,您可能需要新增“I”、“a”和空字串。
  • 為了提高程式的效能,您可能需要記憶已知可縮減的單詞。

您可以在 'thinkpython.com/code/reducible.py' 檢視我的解決方案。


1
在 Python 3.0 中,zip 返回元組的迭代器,但對於大多數目的而言,迭代器表現得像列表。
2
這種行為在 Python 3.0 中略有不同。
3
這個練習的靈感來自 puzzlers.org 上的一個例子。
4
www.cartalk.com/content/puzzler/transcripts/200651
華夏公益教科書