1. 程式人生 > >【練習題】第十二章--元組(Think Python)

【練習題】第十二章--元組(Think Python)

元組

1.元組不可修改

元組是一系列的值。這些值可以是任意型別的,並且用整數序號作為索引,所以可以發現元組和列表非常相似。二者間重要的區別就是元組是不可修改的。

元組的語法是一系列用逗號分隔的值:

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

通常都用一對圓括號把元組的元素包括起來,當然不這樣也沒事。

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

要建立一個單個元素構成的元組,必須要在結尾加上逗號:

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

只用括號放一個值則並不是元組:

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

另一中建立元組的方法是使用內建函式 tuple。不提供引數的情況下,預設就建立一個空的元組。

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

如果引數為一個序列(比如字串、列表或者元組),結果就會得到一個以該序列元素組成的元組。

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

tuple 是內建函式命了,所以你就不能用來作為變數名了。

列表的各種運算子也基本適用於元組。方括號可以用來索引元素:

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

切片運算子也可以用於選取某一區間的元素。

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

但如果你想修改元組中的某個元素,就會得到錯誤了:

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

因為元組是不能修改的,你不能修改其中的元素。但是可以用另一個元組來替換已有的元組。

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

上面這個語句建立了一個新的元組,然後讓 t 指向了這個新的元組。

關係運算符也適用於元組和其他序列;Python 從每個元素的首個元素開始對比。如果相等,就對比下一個元素,依此類推,之道找到不同元素為止。

有了不同元素之後,後面的其他元素就被忽略掉了(即便很大也沒用)。

>>> (0, 1, 2) < (0, 3, 4)
True
>>> (0, 1, 2000000) < (0, 3, 4)
True

2.元組賦值

對兩個變數的值進行交換是一種常用操作。用常見語句來實現的話,就必須有一個臨時變數。比如下面這個例子中是交換 a 和 b:

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

這樣解決還是挺麻煩的;用元組賦值就更簡潔了:

>>> a, b = b, a

等號左邊的是變數組成的一個元組;右邊的是表示式的元組。每個值都被賦給了對應的變數。等號右邊的表示式的值保留了賦值之前的初始值。

等號左右兩側的變數和值的數目都必須是一樣的。

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

更普適的情況下,等號右邊以是任意一種序列(字串、列表或者元組)。比如,要把一個電子郵件地址轉換成一個使用者名稱和一個域名,可以用如下程式碼實現:

>>> addr = '[email protected]'
>>> uname, domain = addr.split('@')

split 的返回值是一個有兩個元素的列表;第一個元素賦值給了 uname 這個變數,第二個賦值給了 domain 這個變數。

>>> uname
'monty'
>>> domain
'python.org'

3.用元組做返回值

嚴格來說,一個函式只能返回一個值,但如果這個值是一個元組,效果就和返回多個值一樣了。例如,如果你想要將兩個整數相除,計算商和餘數,如果要分開計算 x/y 以及 x%y 就很麻煩了。更好的辦法是同時計算這兩個值。

內建函式 divmod 就會接收兩個引數,然後返回一個有兩個值的元組,這兩個值分別為商和餘數。

可以把結果儲存為一個元組:

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

或者可以用元組賦值來分別儲存這兩個值:

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

下面的例子中,函式返回一個元組作為返回值:

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

max 和 min 都是內建函式,會找到序列中的最大值或者最小值,min_max 這個函式會同時求得最大值和最小值,然後把這兩個值作為元組來返回。

4.引數長度可變的元組

函式的引數可以有任意多個。用星號*開頭來作為形式引數名,可以將所有實際引數收錄到一個元組中。例如 printall 就可以獲取任意多個數的引數,然後把它們都列印輸出:

def printall(*args):
    print(args)

你可以隨意命名收集來的這些引數,但 args 這個是約定俗成的慣例。下面展示一下這個函式如何使用:

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

與聚集相對的就是分散了。如果有一系列的值,然後想把它們作為多個引數傳遞給一個函式,就可以用星號*運算子。比如 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 的函式,讓它可以接收任意數量的引數,返回總和:

def sumall(*t):
    sumall=0
    for i in t:
        sumall+=i
    return sumall

5.列表和元組

zip 是一個內建函式,接收兩個或更多的序列作為引數,然後返回返回一個元組列表,該列表中每個元組都包含了從各個序列中的一個元素。這個函式名的意思就是拉鎖,就是把不相關的兩排拉鎖齒連線到一起。

下面這個例子中,一個字串和一個列表通過 zip 這個函式連線到了一起:

>>> s = 'abc'
>>> t = [0, 1, 2]
>>> zip(s, t)
<zip object at 0x7f7d0a9e7c48>

該函式的返回值是一個 zip 物件,該物件可以用來迭代所有的數值對。zip 函式經常被用到 for 迴圈中:

>>> for pair in zip(s, t): ...
print(pair) ...
('a', 0) ('b', 1) ('c', 2)

zip 物件是一種迭代器,也就是某種可以迭代整個序列的物件。迭代器和列表有些相似,但不同於列表的是,你無法通過索引來選擇迭代器中的指定元素。

如果想用列表的運算子和方法,可以用 zip 物件來構成一個列表:

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

返回值是一個由元組構成的列表;在這個例子中,每個元組都包含了字串中的一個字母,以及列表中對應位置的元素。

在長度不同的序列中,返回的結果長度取決於最短的一個。

>>> list(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 都選中列表中的下一個元組,然後把元素賦值給字母和數字。該迴圈的輸出如下:

0 a 1 b 2 c

如果結合使用 zip、for 迴圈以及元組賦值,就能得到一種能同時遍歷兩個以上序列的程式碼組合。比如下面例子中的 has_match 這個函式,接收兩個序列t1和 t2作為引數,然後如果存在一個索引位置 i 使得 t1[i] == t2[i]就返回真:

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)

enumerate 函式的返回值是一個列舉物件,它會遍歷整個成對序列;每一對都包括一個索引(從0開始)以及給定序列的一個元素。在本節的例子中,輸出依然如下:

0 a 1 b 2 c

6.詞典和元組

字典有一個名為 items 的方法,會返回一個由元組組成的序列,每一個元組都是字典中的一個鍵值對

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

結果是一個 dict_items 物件,這是一個迭代器,迭代所有的鍵值對。可以在 for 迴圈裡面用這個物件,如下所示:

>>> for key, value in d.items():
...     print(key, value)
... c 2 a 0 b 1

你也應該預料到了,字典裡面的項是沒有固定順序的。

反過來使用的話,你就也可以用一個元組的列表來初始化一個新的字典:

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

結合使用 dict 和 zip ,會得到一種建立字典的簡便方法:

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

字典的 update 方法也接收一個元組列表,然後把它們作為鍵值對新增到一個已存在的字典中。

把元組用作字典中的鍵是很常見的做法(主要也是因為這種情況不能用列表)。比如,一個電話字典可能就映射了姓氏、名字的資料對到不同的電話號碼。假如我們定義了 last,first 和 number 這三個變數,可以用如下方法來實現:

directory[last, first] = number

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

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

上面這個迴圈會遍歷字典中的鍵,這些鍵都是元組。程式會把每個元組的元素分別賦值給 last 和 first,然後輸出名字以及對應的電話號。

7.由序列組成的序列

之前我一直在講由元組組成的列表,但本章幾乎所有的例子也適用於由列表組成的列表、元組組成的元組以及列表組成的元組。為了避免列舉所有的組合,咱們直接討論序列組成的序列就更方便一些。

很多情況下,不同種類的序列(字串、列表和元組)是可以交換使用的。那麼該如何選擇用哪種序列呢?

先從最簡單的開始,字串比起其他序列,功能更加有限,因為字串中的元素必須是字元。而且還不能修改。如果你要修改字串裡面的字元(而不是要建立一個新字串),你最好還是用字元列表吧。

列表用的要比元組更廣泛,主要因為列表可以修改。但以下這些情況下,你還是用元組更好:

在某些情況下,比如返回語句中,用元組來實現語法上要比列表簡單很多。

如果你要用一個序列作為字典的鍵,必須用元組或者字串這樣不可修改的型別才行。

如果你要把一個序列作為引數傳給一個函式,用元組能夠降低由於別名使用導致未知情況而帶來的風險。

由於元組是不可修改的,所以不提供 sort 和 reverse 這樣的方法,這些方法都只能修改已經存在的列表。但 Python 提供了內建函式 sorted,該函式接收任意序列,然後返回一個把該序列中元素重新排序過的列表,另外還有個內建函式 reversed,接收一個序列然後返回一個以逆序迭代整個列表的迭代器。

8.除錯

列表、字典以及元組,都是資料結構的一些樣例;在本章我們開始見識這些複合的資料結構,比如由元組組成的列表,或者包含元組作為鍵而列表作為鍵值的字典等等。符合資料結構非常有用,但容易導致一些錯誤,我把這種錯誤叫做結構錯誤;這種錯誤往往是由於一個數據結構中出現了錯誤的型別、大小或者結構而引起的。比如,如果你想要一個由一個整形構成的列表,而我給你一個單純的整形變數(不是放進列表的),就會出錯了。

要想有助於解決這類錯誤,我寫了一個叫做structshape 的模組,該模組提供了一個同名函式,接收任何一種資料結構作為引數,然後返回一個字串來總結該資料結構的形態。可以從http://greenteapress.com/thinkpython2/code/structshape.py下載。

下面是一個簡單列表的示範:

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

更帶勁點的程式可能還應該寫“list of 3 ints”,但不理會單複數變化有利於簡化問題。下面是一個列表的列表:

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

如果列表元素是不同型別,structshape 會按照順序,把每種型別都列出:

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

下面是一個元組的列表:

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

下面是一個有三個項的字典,該字典映射了從整形數到字串。

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

如果你追蹤自己的資料結構有困難,structshape這個模組能有所幫助。