# Chapter 2 內置數據類型
> " Wonder is the foundation of all philosophy, inquiry its progress, ignorance its end. "
> — Michel de Montaigne
## 深入
讓我們暫時將 [第一份 Python 程序](your-first-python-program.html) 拋在腦后,來聊一聊數據類型。在 Python 中, [每個值都有一種數據類型](your-first-python-program.html#declaringfunctions),但您并不需要聲明變量的數據類型。那該方式是如何運作的呢?Python 根據每個變量的初始賦值情況分析其類型,并在內部對其進行跟蹤。
Python 有多種內置數據類型。以下是比較重要的一些:
1. **Booleans[布爾型]** 或為 `True[真]` 或為 `False[假]`。
2. **Numbers[數值型]** 可以是 Integers[整數](`1` 和 `2`)、Floats[浮點數](`1.1` 和 `1.2`)、Fractions[分數](`1/2` 和 `2/3`);甚至是 [Complex Number[復數]](http://en.wikipedia.org/wiki/Complex_number)。
3. **Strings[字符串型]** 是 Unicode 字符序列,_例如:_ 一份 HTML 文檔。
4. **Bytes[字節]** 和 **Byte Arrays[字節數組]**, _例如:_ 一份 JPEG 圖像文件。
5. **Lists[列表]** 是值的有序序列。
6. **Tuples[元組]** 是有序而不可變的值序列。
7. **Sets[集合]** 是裝滿無序值的包裹。
8. **Dictionaries[字典]** 是鍵值對的無序包裹。
當然,還有更多的類型。在 Python 中[一切均為對象](your-first-python-program.html#everythingisanobject),因此存在像 _module[模塊]_、 _function[函數]_、 _class[類]_、 _method[方法]_、 _file[文件]_ 甚至 _compiled code[已編譯代碼]_ 這樣的類型。您已經見過這樣一些例子:[模塊的 name](your-first-python-program.html#runningscripts)、 [函數的 `docstrings`](your-first-python-program.html#docstrings) _等等_。將學到的包括 [《類 _與_ 迭代器》](iterators.html) 中的 Classes[類],以及 [《文件》](files.html) 中的 Files[文件]。
Strings[字符串]和 Bytes[字節串]比較重要,也相對復雜,足以開辟獨立章節予以講述。讓我們先看看其它類型。
## 布爾類型
在布爾類型上下文中,您幾乎可以使用任何表達式。
布爾類型或為真或為假。Python 有兩個被巧妙地命名為 `True` 和 `False` 的常量,可用于對布爾類型的直接賦值。表達式也可以計算為布爾類型的值。在某些地方(如 `if` 語句),Python 所預期的就是一個可計算出布爾類型值的表達式。這些地方稱為 _布爾類型上下文環境_。事實上,可在布爾類型上下文環境中使用任何表達式,而 Python 將試圖判斷其真值。在布爾類型上下文環境中,不同的數據類型對于何值為真、何值為假有著不同的規則。(看過本章稍后的實例后,這一點將更好理解。)
例如,看看 [`humansize.py`](your-first-python-program.html#divingin) 中的這個片段:
```
if size < 0:
raise ValueError('number must be non-negative')
```
`size` 是整數, 0 是整數,而 `<` 是數字運算符。`size < 0` 表達式的結果始終是布爾值。可在 Python 交互式 shell 中自行測試下結果:
```
>>> size = 1
>>> size < 0
False
>>> size = 0
>>> size < 0
False
>>> size = -1
>>> size < 0
True
```
由于 Python 2 的一些遺留問題,布爾值可以當做數值對待。`True` 為 `1`;`False` 為 0 。
```
>>> True + True
2
>>> True - False
1
>>> True * False
0
>>> True / False
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: int division or modulo by zero
```
喔,喔,喔!別那么干。忘掉我剛才說的。
## 數值類型
數值類型是可畏的。有太多類型可選了。Python 同時支持 Integer[整型] 和 Floating Point[浮點型] 數值。無任何類型聲明可用于區分;Python 通過是否有 小數 點來分辨它們。
```
<class 'int'>
True
2
2.0
>>> type(2.0)
<class 'float'>
```
1. 可以使用 `type()` 函數來檢測任何值或變量的類型。正如所料,`1` 為 `int` 類型。
2. 同樣,還可使用 `isinstance()` 函數判斷某個值或變量是否為給定某個類型。
3. 將一個 `int` 與一個 `int` 相加將得到一個 `int` 。
4. 將一個 `int` 與一個 `float` 相加將得到一個 `float` 。Python 把 `int` 強制轉換為 `float` 以進行加法運算;然后返回一個 `float` 類型的結果。
### 將整數強制轉換為浮點數及反向轉換
正如剛才所看到的,一些運算符(如:加法)會根據需把整數強制轉換為浮點數。也可自行對其進行強制轉換。
```
2.0
2
2
-2
1.1234567890123457
<class 'int'>
```
1. 通過調用`float()` 函數,可以顯示地將 `int` 強制轉換為 `float`。
2. 毫不出奇,也可以通過調用 `int()` 將 `float` 強制轉換為 `int` 。
3. `int()` 將進行取整,而不是四舍五入。
4. 對于負數,`int()` 函數朝著 0 的方法進行取整。它是個真正的取整(截斷)函數,而不是 floor[地板]函數。
5. 浮點數精確到小數點后 15 位。
6. 整數可以任意大。
> ?Python 2 對于`int[整型]` 和 `long[長整型]` 采用不同的數據類型。`int` 數據類型受到 `sys.maxint` 的限制,因平臺該限制也會有所不同,但通常是 `2**32-1` 。Python 3 只有一種整數類型,其行為方式很有點像 Python 2 的舊 `long[長整數]` 類型。參閱 [PEP 237](http://www.python.org/dev/peps/pep-0237) 了解更多細節。
### 常見數值運算
對數值可進行各種類型的運算。
```
5.5
5
?6
5.0
121
1
```
1. `/` 運算符執行浮點除法。即便分子和分母都是 `int`,它也返回一個 `float` 浮點數。
2. `//` 運算符執行古怪的整數除法。如果結果為正數,可將其視為朝向小數位取整(不是四舍五入),但是要小心這一點。
3. 當整數除以負數, `//` 運算符將結果朝著最近的整數“向上”四舍五入。從數學角度來說,由于 `?6` 比 `?5` 要小,它是“向下”四舍五入,如果期望將結果取整為 `?5`,它將會誤導你。
4. `//` 運算符并非總是返回整數結果。如果分子或者分母是 `float`,它仍將朝著最近的整數進行四舍五入,但實際返回的值將會是 `float` 類型。
5. `**` 運算符的意思是“計算冪”,`11**2` 結果為 `121` 。
6. `%` 運算符給出了進行整除之后的余數。`11` 除以 `2` 結果為 `5` 以及余數 `1`,因此此處的結果為 `1`。
> ?在 Python 2 中,運算符 `/` 通常表示整數除法,但是可以通過在代碼中加入特殊指令,使其看起來像浮點除法。在 Python 3 中,`/` 運算符總是表示浮點除法。參閱 [PEP 238](http://www.python.org/dev/peps/pep-0238/) 了解更多細節。
### 分數
Python 并不僅僅局限于整數和浮點數類型。它可以完成你在高中階段學過、但幾乎已經全部忘光的所有古怪數學運算。
```
>>> x
Fraction(1, 3)
Fraction(2, 3)
Fraction(3, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "fractions.py", line 96, in __new__
raise ZeroDivisionError('Fraction(%s, 0)' % numerator)
ZeroDivisionError: Fraction(0, 0)
```
1. 為啟用 fractions 模塊,必先引入 `fractions` 模塊。
2. 為定義一個分數,創建一個 `Fraction` 對象并傳入分子和分母。
3. 可對分數進行所有的常規數學計算。運算返回一個新的 `Fraction` 對象。`2 * (1/3) = (2/3)`
4. `Fraction` 對象將會自動進行約分。`(6/4) = (3/2)`
5. 在杜絕創建以零為分母的分數方面,Python 有著良好的敏感性。
### 三角函數
還可在 Python 中進行基本的三角函數運算。
```
>>> import math
3.1415926535897931
1.0
0.99999999999999989
```
1. `math` 模塊中有一個代表 π 的常量,表示圓的周長與直徑之比率(圓周率)。
2. `math` 模塊包括了所有的基本三角函數,包括:`sin()`、 `cos()`、`tan()` 及像 `asin()` 這樣的變體函數。
3. 然而要注意的是 Python 并不支持無限精度。`tan(π / 4)` 將返回 `1.0`,而不是 `0.99999999999999989`。
### 布爾上下文環境中的數值
零值是 false[假],非零值是 true[真]。
可以在 `if` 這樣的 [布爾類型上下文環境中](#booleans) 使用數值。零值是 false[假],非零值是 true[真]。
```
... if anything:
... print("yes, it's true")
... else:
... print("no, it's false")
...
yes, it's true
>>> is_it_true(-1)
yes, it's true
>>> is_it_true(0)
no, it's false
yes, it's true
>>> is_it_true(0.0)
no, it's false
>>> import fractions
yes, it's true
>>> is_it_true(fractions.Fraction(0, 1))
no, it's false
```
1. 您知道可以在 Python 交互式 Shell 中定義自己的函數嗎?只需在每行的結尾按 `回車鍵` ,然后在某一空行按 `回車鍵` 結束。
2. 在布爾類型上下文環境中,非零整數為真;零為假。
3. 非零浮點數為真; `0.0` 為假。請千萬小心這一點!如果有輕微的四舍五入偏差(正如在前面小節中看到的那樣,這并非不可能的事情),那么 Python 將測試 `0.0000000000001` 而不是 0 ,并將返回一個 `True` 值。
4. 分數也可在布爾類型上下文環境中使用。無論 `n` 為何值,`Fraction(0, n)` 為假。所有其它分數為真。
## 列表
列表是 Python 的主力數據類型。當提到 “列表 ”時,您腦海中可能會閃現“必須進一步聲明大小的數組,只能包含同一類對象“ 等想法。千萬別這么想。列表比那要酷得多。
> ? Python 中的列表類似 Perl 5 中的數組。在 Perl 5 中,存儲數組的變量總是以字符 `@` 開頭;在 Python 中,變量可隨意命名,Python 僅在內部對數據類型進行跟蹤。
> ? Python 中的列表更像 Java 中的數組(盡管可以把列表當做生命中所需要的一切來使用)。一個更好的比喻可能是 `ArrayList` 類,該類可以容納任何對象,并可在添加新元素時進行動態拓展。
### 創建列表
列表創建非常輕松:使用中括號包裹一系列以逗號分割的值即可。
```
>>> a_list
['a', 'b', 'mpilgrim', 'z', 'example']
'a'
'example'
'example'
'mpilgrim'
```
1. 首先,創建一個包含 5 個元素的列表。要注意的是它們保持了最初的順序。這并不是偶然的。列表是元素的有序集合。
2. 列表可當做以零為基點的數組使用。非空列表的首個元素始終是 `a_list[0]` 。
3. 該 5 元素列表的最后一個元素是 `a_list[4]`,因為列表(索引)總是以零為基點的。
4. 使用負索引值可從列表的尾部向前計數訪問元素。任何非空列表的最后一個元素總是 `a_list[-1]` 。
5. 如果負數令你混淆,可將其視為如下方式: `a_list[-`n`] == a_list[len(a_list) - `n`]` 。因此在此列表中, `a_list[-3] == a_list[5 - 3] == a_list[2]`。
### 列表切片
a_list[0] 是列表的第一個元素。
定義列表后,可從其中獲取任何部分作為新列表。該技術稱為對列表進行 _切片_ 。
```
>>> a_list
['a', 'b', 'mpilgrim', 'z', 'example']
['b', 'mpilgrim']
['b', 'mpilgrim', 'z']
['a', 'b', 'mpilgrim']
['a', 'b', 'mpilgrim']
['z', 'example']
['a', 'b', 'mpilgrim', 'z', 'example']
```
1. 通過指定兩個索引值,可以從列表中獲取稱作“切片”的某個部分。返回值是一個新列表,它包含列表(??切片)中所有元素,按順序從第一個切片索引開始(本例中為 `a_list[1]`),截止但不包含第二個切片索引(本例中的 `a_list[3]`)。
2. 如果切片索引之一或兩者均為負數,切片操作仍可進行。如果有幫助的話,您可以這么思考:自左向右讀取列表,第一個切片索引指明了想要的第一個元素,第二個切片索引指明了第一個不想要的元素。返回值是兩者之間的任何值。 between.
3. 列表是以零為起點的,因此 `a_list[0:3]` 返回列表的頭三個元素,從 `a_list[0]` 開始,截止到但不包括 `a_list[3]` 。
4. 如果左切片索引為零,可以將其留空而將零隱去。因此 `a_list[:3]` 與 `a_list[0:3]` 是完全相同的,因為起點 0 被隱去了。
5. 同樣,如果右切片索引為列表的長度,也可以將其留空。因此 `a_list[3:]` 與 `a_list[3:5]` 是完全相同的,因為該列表有五個元素。此處有個好玩的對稱現象。在這個五元素列表中, `a_list[:3]` 返回頭三個元素,而 `a_list[3:]` 返回最后兩個元素。事實上,無論列表的長度是多少, `a_list[:`n`]` 將返回頭 `n` 個元素,而 `a_list[`n`:]` 返回其余部分。
6. 如果兩個切片索引都留空,那么將包括列表所有的元素。但該返回值與最初的 `a_list` 變量并不一樣。它是一個新列表,只不過恰好擁有完全相同的元素而已。`a_list[:]` 是對列表進行復制的一條捷徑。
### 向列表中新增項
有四種方法可用于向列表中增加元素。
```
>>> a_list = ['a']
['a', 2.0, 3]
>>> a_list
['a', 2.0, 3, True]
>>> a_list
['a', 2.0, 3, True, 'four', 'Ω']
>>> a_list
['Ω', 'a', 2.0, 3, True, 'four', 'Ω']
```
1. `+` 運算符連接列表以創建一個新列表。列表可包含任何數量的元素;沒有大小限制(除了可用內存的限制)。然而,如果內存是個問題,那就必須知道在進行連接操作時,將在內存中創建第二個列表。在該情況下,新列表將會立即被賦值給已有變量 `a_list` 。因此,實際上該行代碼包含兩個步驟?—?連接然后賦值?—?當處理大型列表時,該操作可能(暫時)消耗大量內存。
2. 列表可包含任何數據類型的元素,單個列表中的元素無須全為同一類型。下面的列表中包含一個字符串、一個浮點數和一個整數。
3. `append()` 方法向列表的尾部添加一個新的元素。(現在列表中有 _四種_ 不同數據類型!)
4. 列表是以類的形式實現的。“創建”列表實際上是將一個類實例化。因此,列表有多種方法可以操作。`extend()` 方法只接受一個列表作為參數,并將該參數的每個元素都添加到原有的列表中。
5. `insert()` 方法將單個元素插入到列表中。第一個參數是列表中將被頂離原位的第一個元素的位置索引。列表中的元素并不一定要是唯一的;比如說:現有兩個各自獨立的元素,其值均為 `'Ω'`:,第一個元素 `a_list[0]` 以及最后一個元素 `a_list[6]` 。
> ?``a_list`.insert(0, `value`)` 就像是 Perl 中的 `unshift()` 函數。它將一個元素添加到列表的頭部,所有其它的元素都被頂理原先的位置以騰出空間。
讓我們進一步看看 `append()` 和 `extend()` 的區別。
```
>>> a_list = ['a', 'b', 'c']
>>> a_list
['a', 'b', 'c', 'd', 'e', 'f']
6
>>> a_list[-1]
'f'
>>> a_list
['a', 'b', 'c', 'd', 'e', 'f', ['g', 'h', 'i']]
7
>>> a_list[-1]
['g', 'h', 'i']
```
1. `extend()` 方法只接受一個參數,而該參數總是一個列表,并將列表 `a_list` 中所有的元素都添加到該列表中。
2. 如果開始有個 3 元素列表,然后將它與另一個 3 元素列表進行 extend 操作,結果是將獲得一個 6 元素列表。
3. 另一方面, `append()` 方法只接受一個參數,但可以是任何數據類型。在此,對一個 3 元素列表調用 `append()` 方法。
4. 如果開始的時候有個 6 元素列表,然后將一個列表 append[添加]上去,結果就會……得到一個 7 元素列表。為什么是 7 個?因為最后一個元素(剛剛 append[添加] 的元素) _本身是個列表_ 。列表可包含任何類型的數據,包括其它列表。這可能是你所需要的結果,也許不是。但如果這就是你想要的,那這就是你所得到的。
### 在列表中檢索值
```
>>> a_list = ['a', 'b', 'new', 'mpilgrim', 'new']
2
True
>>> 'c' in a_list
False
3
2
Traceback (innermost last):
File "<interactive input>", line 1, in ?ValueError: list.index(x): x not in list
```
1. 如你所期望, `count()` 方法返回了列表中某個特定值出現的次數。
2. 如果你想知道的是某個值是否出現在列表中, `in` 運算符將會比使用 `count()` 方法要略快一些。`in` 運算符總是返回 `True` 或 `False`;它不會告訴你該值出現在什么位置。
3. 如果想知道某個值在列表中的精確位置,可調用 `index()` 方法。盡管可以通過第二個參數(以 0 為基點的)索引值來指定起點,通過第三個參數(以 0 基點的)索引來指定搜索終點,但缺省情況下它將搜索整個列表,
4. `index()` 方法將查找某值在列表中的_第一次_出現。在該情況下,`'new'` 在列表中出現了兩次,分別為 `a_list[2]` 和 `a_list[4]`,但 `index()` 方法將只返回第一次出現的位置索引值。
5. 可能 _出乎_ 您的預期,如果在列表中沒有找到該值,`index()` 方法將會引發一個例外。
等等,什么?是這樣的:如果沒有在列表中找到該值, `index()` 方法將會引發一個例外。這是 Python 語言最顯著不同之處,其它多數語言將會返回一些無效的索引值(像是 `-1`)。當然,一開始這一點看起來比較討厭,但我想您會逐漸欣賞它。這意味著您的程序將會在問題的源頭處崩潰,而不是之后奇怪地、默默地崩潰。請記住, [`-1` 是合法的列表索引值](#creatinglists)。如果 `index()` 方法返回 `-1`,可能會導致調整過程變得不那么有趣!
### 從列表中刪除元素
列表永遠不會有縫隙。
列表可以自動拓展或者收縮。您已經看到了拓展部分。也有幾種方法可從列表中刪除元素。
```
>>> a_list = ['a', 'b', 'new', 'mpilgrim', 'new']
>>> a_list[1]
'b'
>>> a_list
['a', 'new', 'mpilgrim', 'new']
'new'
```
1. 可使用 `del` 語句從列表中刪除某個特定元素。
2. 刪除索引 `1` 之后再訪問索引 `1` 將 _不會_ 導致錯誤。被刪除元素之后的所有元素將移動它們的位置以“填補”被刪除元素所產生的“縫隙”。
不知道位置索引?這不成問題,您可以通過值而不是索引刪除元素。
```
>>> a_list
['a', 'mpilgrim', 'new']
>>> a_list
['a', 'mpilgrim']
>>> a_list.remove('new')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list
```
1. 還可以通過 `remove()` 方法從列表中刪除某個元素。`remove()` 方法接受一個 _value_ 參數,并刪除列表中該值的第一次出現。同樣,被刪除元素之后的所有元素將會將索引位置下移,以“填補縫隙”。列表永遠不會有“縫隙”。
2. 您可以盡情地調用 `remove()` 方法,但如果試圖刪除列表中不存在的元素,它將引發一個例外。
### Removing Items From A List: Bonus Round
另一有趣的列表方法是 `pop()` 。`pop()` 方法是[從列表刪除元素](#removingfromlists)的另一方法,但有點變化。
```
>>> a_list = ['a', 'b', 'new', 'mpilgrim']
'mpilgrim'
>>> a_list
['a', 'b', 'new']
'b'
>>> a_list
['a', 'new']
>>> a_list.pop()
'new'
>>> a_list.pop()
'a'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: pop from empty list
```
1. 如果不帶參數調用, `pop()` 列表方法將刪除列表中最后的元素,_并返回所刪除的值_。
2. 可以從列表中 pop[彈出]任何元素。只需傳給 `pop()` 方法一個位置索引值。它將刪除該元素,將其后所有元素移位以“填補縫隙”,然后返回它刪除的值。
3. 對空列表調用 `pop()` 將會引發一個例外。
> ?不帶參數調用的 `pop()` 列表方法就像 Perl 中的 `pop()` 函數。它從列表中刪除最后一個元素并返回所刪除元素的值。Perl 還有另一個函數 `shift()`,可用于刪除第一個元素并返回其值;在 Python 中,該函數相當于 ``a_list`.pop(0)` 。
### 布爾上下文環境中的列表
空列表為假;其它所有列表為真。
可以在 `if` 這樣的 [布爾類型上下文環境中](#booleans) 使用列表。
```
>>> def is_it_true(anything):
... if anything:
... print("yes, it's true")
... else:
... print("no, it's false")
...
no, it's false
yes, it's true
yes, it's true
```
1. 在布爾類型上下文環境中,空列表為假值。
2. 任何至少包含一個上元素的列表為真值。
3. 任何至少包含一個上元素的列表為真值。元素的值無關緊要。
## 元組
元素 是不可變的列表。一旦創建之后,用任何方法都不可以修改元素。
```
>>> a_tuple
('a', 'b', 'mpilgrim', 'z', 'example')
'a'
'example'
('b', 'mpilgrim')
```
1. 元組的定義方式和列表相同,除了整個元素的集合都用圓括號,而不是方括號閉合。
2. 和列表一樣,元組的元素都有確定的順序。元組的索引也是以零為基點的,和列表一樣,因此非空元組的第一個元素總是 `a_tuple[0]` 。
3. 負的索引從元組的尾部開始計數,這和列表也是一樣的。
4. 和列表一樣,元組也可以進行切片操作。對列表切片可以得到新的列表;對元組切片可以得到新的元組。
元組和列表的主要區別是元組不能進行修改。用技術術語來說,元組是 不可變更 的。從實踐的角度來說,沒有可用于修改元組的方法。列表有像 `append()`、 `extend()`、 `insert()`、`remove()` 和 `pop()` 這樣的方法。這些方法,元組都沒有。可以對元組進行切片操作(因為該方法創建一個新的元組),可以檢查元組是否包含了特定的值(因為該操作不修改元組),還可以……就那么多了。
```
# continued from the previous example
>>> a_tuple
('a', 'b', 'mpilgrim', 'z', 'example')
Traceback (innermost last):
File "<interactive input>", line 1, in ?AttributeError: 'tuple' object has no attribute 'append'
Traceback (innermost last):
File "<interactive input>", line 1, in ?AttributeError: 'tuple' object has no attribute 'remove'
4
True
```
1. 無法向元組添加元素。元組沒有 `append()` 或 `extend()` 方法。
2. 不能從元組中刪除元素。元組沒有 `remove()` 或 `pop()` 方法。
3. _可以_ 在元組中查找元素,由于該操作不改變元組。
4. 還可以使用 `in` 運算符檢查某元素是否存在于元組中。
那么元組有什么好處呢?
* 元組的速度比列表更快。如果定義了一系列常量值,而所需做的僅是對它進行遍歷,那么請使用元組替代列表。
* 對不需要改變的數據進行“寫保護”將使得代碼更加安全。使用元組替代列表就像是有一條隱含的 `assert` 語句顯示該數據是常量,特別的想法(及特別的功能)必須重寫。(??)
* 一些元組可用作字典鍵(特別是包含字符串、數值和其它元組這樣的_不可變_數據的元組)。列表永遠不能當做字典鍵使用,因為列表不是不可變的。
> ?元組可轉換成列表,反之亦然。內建的 `tuple()` 函數接受一個列表參數,并返回一個包含同樣元素的元組,而 `list()` 函數接受一個元組參數并返回一個列表。從效果上看, `tuple()` 凍結列表,而 `list()` 融化元組。
### 布爾上下文環境中的元組
可以在 `if` 這樣的 [布爾類型上下文環境中](#booleans) 使用元組。
```
>>> def is_it_true(anything):
... if anything:
... print("yes, it's true")
... else:
... print("no, it's false")
...
no, it's false
yes, it's true
yes, it's true
<class 'bool'>
>>> type((False,))
<class 'tuple'>
```
1. 在布爾類型上下文環境中,空元組為假值。
2. 任何至少包含一個上元素的元組為真值。
3. 任何至少包含一個上元素的元組為真值。元素的值無關緊要。不過此處的逗號起什么作用呢?
4. 為創建單元素元組,需要在值之后加上一個逗號。沒有逗號,Python 會假定這只是一對額外的圓括號,雖然沒有害處,但并不創建元組。
### 同時賦多個值
以下是一種很酷的編程捷徑:在 Python 中,可使用元組來一次賦多值。
```
>>> v = ('a', 2, True)
>>> x
'a'
>>> y
2
>>> z
True
```
1. `v` 是一個三元素的元組,而 `(x, y, z)` 是包含三個變量的元組。將其中一個賦值給另一個將會把 `v` 中的每個值按順序賦值給每一個變量。
該特性有多種用途。假設需要將某個名稱指定某個特定范圍的值。可以使用內建的 `range()` 函數進行多變量賦值以快速地進行連續變量賦值。
```
0
>>> TUESDAY
1
>>> SUNDAY
6
```
1. 內建的 `range()` 函數構造了一個整數序列。(從技術上來說, `range()` 函數返回的既不是列表也不是元組,而是一個 [迭代器](iterators.html),但稍后您將學到它們的區別。) `MONDAY`、 `TUESDAY`、 `WEDNESDAY`、 `THURSDAY`、 `FRIDAY`、 `SATURDAY` 和 `SUNDAY` 是您所定義的變量。(本例來自于 `calendar` 模塊,該短小而有趣的模塊打印日歷,有點像 UNIX 程序 `cal` 。該 `calendar` 模塊為星期數定義了整數常量。
2. 現在,每個變量都有其值了: `MONDAY` 為 0, `TUESDAY` 為 `1`,如此類推。
還可以使用多變量賦值創建返回多值的函數,只需返回一個包含所有值的元組。調用者可將返回值視為一個簡單的元組,或將其賦值給不同的變量。許多標準 Python 類庫這么干,包括在[下一章](comprehensions.html#os)將學到的 `os` 模塊。
## 集合
集合set 是裝有獨特值的無序“袋子”。一個簡單的集合可以包含任何數據類型的值。如果有兩個集合,則可以執行像聯合、交集以及集合求差等標準集合運算。
### 創建集合
重中之重。創建集合非常簡單。
```
>>> a_set
{1}
<class 'set'>
>>> a_set
{1, 2}
```
1. 要創建只包含一個值的集合,僅需將該值放置于花括號之間。(`{}`)。
2. 實際上,集合以 [類](iterators.html#defining-classes) 的形式實現,但目前還無須考慮這一點。
3. 要創建多值集合,請將值用逗號分開,并用花括號將所有值包裹起來。
還可以 [列表](#lists) 為基礎創建集合。
```
>>> a_list = ['a', 'b', 'mpilgrim', True, False, 42]
{'a', False, 'b', True, 'mpilgrim', 42}
['a', 'b', 'mpilgrim', True, False, 42]
```
1. 要從列表創建集合,可使用 `set()` 函數。(懂得如何實現集合的學究可能指出這實際上并不是調用某個函數,而是對某個類進行實例化。我_保證_在本書稍后的地方將會學到其中的區別。目前而言,僅需知道 `set()` 行為與函數類似,以及它返回一個集合。)
2. 正如我之前提到的,簡單的集合可以包括任何數據類型的值。而且,如我之前所提到的,集合是 _無序的_。該集合并不記得用于創建它的列表中元素的最初順序。如果向集合中添加元素,它也不會記得添加的順序。
3. 初始的列表并不會發生變化。
還沒有任何值?沒有問題。可以創建一個空的集合。
```
set()
<class 'set'>
0
>>> type(not_sure)
<class 'dict'>
```
1. 要創建空集合,可不帶參數調用 `set()` 。
2. 打印出來的空集合表現形式看起來有點兒怪。也許,您期望看到一個 `{}` 吧 ?該符號表示一個空的字典,而不是一個空的集合。本章稍后您將學到關于字典的內容。
3. 盡管打印出的形式奇怪,這 _確實是_ 一個集合……
4. …… 同時該集合沒有任何成員。
5. 由于從 Python 2 沿襲而來歷史的古怪規定,不能使用兩個花括號來創建空集合。該操作實際創建一個空字典,而不是一個空集合。
### 修改集合
有兩種方法可向現有集合中添加值: `add()` 方法和 `update()` 方法。
```
>>> a_set = {1, 2}
>>> a_set
{1, 2, 4}
3
>>> a_set
{1, 2, 4}
3
```
1. `add()` 方法接受單個可以是任何數據類型的參數,并將該值添加到集合之中。
2. 該集合現在有三個成員了。
3. 集合是裝 _唯一值_ 的袋子。如果試圖添加一個集合中已有的值,將不會發生任何事情。將不會引發一個錯誤;只是一條空操作。
4. 該集合 _仍然_ 只有三個成員。
```
>>> a_set = {1, 2, 3}
>>> a_set
{1, 2, 3}
{1, 2, 3, 4, 6}
>>> a_set
{1, 2, 3, 4, 5, 6, 8, 9, 13}
>>> a_set
{1, 2, 3, 4, 5, 6, 8, 9, 10, 13, 20, 30}
```
1. `update()` 方法僅接受一個集合作為參數,并將其所有成員添加到初始列表中。其行為方式就像是對參數集合中的每個成員調用 `add()` 方法。
2. 由于集合不能包含重復的值,因此重復的值將會被忽略。
3. 實際上,可以帶任何數量的參數調用 `update()` 方法。如果調用時傳遞了兩個集合, `update()` 將會被每個集合中的每個成員添加到初始的集合當中(丟棄重復值)。
4. `update()` 方法還可接受一些其它數據類型的對象作為參數,包括列表。如果調用時傳入列表,`update()` 將會把列表中所有的元素添加到初始集合中。
### 從集合中刪除元素
有三種方法可以用來從集合中刪除某個值。前兩種,`discard()` 和 `remove()` 有細微的差異。
```
>>> a_set = {1, 3, 6, 10, 15, 21, 28, 36, 45}
>>> a_set
{1, 3, 36, 6, 10, 45, 15, 21, 28}
>>> a_set
{1, 3, 36, 6, 45, 15, 21, 28}
>>> a_set
{1, 3, 36, 6, 45, 15, 21, 28}
>>> a_set
{1, 3, 36, 6, 45, 15, 28}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 21
```
1. `discard()` 接受一個單值作為參數,并從集合中刪除該值。
2. 如果針對一個集合中不存在的值調用 `discard()` 方法,它不進行任何操作。不產生錯誤;只是一條空指令。
3. `remove()` 方法也接受一個單值作為參數,也從集合中將其刪除。
4. 區別在這里:如果該值不在集合中,`remove()` 方法引發一個 `KeyError` 例外。
就像列表,集合也有個 `pop()` 方法。
```
>>> a_set = {1, 3, 6, 10, 15, 21, 28, 36, 45}
1
>>> a_set.pop()
3
>>> a_set.pop()
36
>>> a_set
{6, 10, 45, 15, 21, 28}
>>> a_set
set()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'pop from an empty set'
```
1. `pop()` 方法從集合中刪除某個值,并返回該值。然而,由于集合是無序的,并沒有“最后一個”值的概念,因此無法控制刪除的是哪一個值。它基本上是隨機的。
2. `clear()` 方法刪除集合中 _所有_ 的值,留下一個空集合。它等價于 `a_set = set()`,該語句創建一個新的空集合,并用之覆蓋 `a_set` 變量的之前的值。
3. 試圖從空集合中彈出某值將會引發 `KeyError` 例外。
### 常見集合操作
Python 的 `集合` 類型支持幾種常見的運算。
```
>>> a_set = {2, 4, 5, 9, 12, 21, 30, 51, 76, 127, 195}
True
>>> 31 in a_set
False
>>> b_set = {1, 2, 3, 5, 6, 8, 9, 12, 15, 17, 18, 21}
{1, 2, 195, 4, 5, 6, 8, 12, 76, 15, 17, 18, 3, 21, 30, 51, 9, 127}
{9, 2, 12, 5, 21}
{195, 4, 76, 51, 30, 127}
{1, 3, 4, 6, 8, 76, 15, 17, 18, 195, 127, 30, 51}
```
1. 要檢測某值是否是集合的成員,可使用 `in` 運算符。其工作原理和列表的一樣。
2. `union()` 方法返回一個新集合,其中裝著 _在兩個_ 集合中出現的元素。
3. `intersection()` 方法返回一個新集合,其中裝著 _同時_ 在兩個集合中出現的所有元素。
4. `difference()` 方法返回的新集合中,裝著所有在 `a_set` 出現但未在 `b_set` 中的元素。
5. `symmetric_difference()` 方法返回一個新集合,其中裝著所有 _只在其中一個_ 集合中出現的元素。
這三種方法是對稱的。
```
# continued from the previous example
{3, 1, 195, 4, 6, 8, 76, 15, 17, 18, 51, 30, 127}
True
True
True
False
```
1. `a_set` 與 `b_set` 的對稱差分 _看起來_ 和`b_set` 與 `a_set` 的對稱差分不同,但請記住:集合是無序的。任何兩個包含所有同樣值(無一遺漏)的集合可認為是相等的。
2. 而這正是這里發生的事情。不要被 Python Shell 對這些集合的輸出形式所愚弄了。它們包含相同的值,因此是相等的。
3. 對兩個集合的 Union[并集]操作也是對稱的。
4. 對兩個集合的 Intersection[交集]操作也是對稱的。
5. 對兩個集合的 Difference[求差]操作不是對稱的。這是有意義的;它類似于從一個數中減去另一個數。操作數的順序會導致結果不同。
最后,有幾個您可能會問到的問題。
```
>>> a_set = {1, 2, 3}
>>> b_set = {1, 2, 3, 4}
True
True
>>> a_set.issubset(b_set)
False
>>> b_set.issuperset(a_set)
False
```
1. `a_set` 是 `b_set`?的 子集 —?所有 `a_set` 的成員均為 `b_set` 的成員。
2. 同樣的問題反過來說, `b_set` 是 `a_set` 的 超集,因為 `a_set` 的所有成員均為 `b_set` 的成員。
3. 一旦向 `a_set` 添加一個未在 `b_set` 中出現的值,兩項測試均返回 `False` 。
### 布爾上下文環境中的集合
可在 `if` 這樣的 [布爾類型上下文環境中](#booleans) 使用集合。
```
>>> def is_it_true(anything):
... if anything:
... print("yes, it's true")
... else:
... print("no, it's false")
...
no, it's false
yes, it's true
yes, it's true
```
1. 在布爾類型上下文環境中,空集合為假值。
2. 任何至少包含一個上元素的集合為真值。
3. 任何至少包含一個上元素的集合為真值。元素的值無關緊要。
## 字典
字典 是鍵值對的無序集合。向字典添加一個鍵的同時,必須為該鍵增添一個值。(之后可隨時修改該值。) Python 的字典為通過鍵獲取值進行了優化,而不是反過來。
> ?Python 中的字典與 Perl 5 中的 hash [散列]類似。在 Perl 5 中,散列存儲的變量總是以一個 `%` 符開頭。在 Python 中,變量可以隨意命名,而 Python 內部跟蹤其數據類型。
### 創建字典
創建字典非常簡單。其語法與 [集合](#sets) 的類似,但應當指定鍵值對而不是值。有了字典后,可以通過鍵來查找值。
```
>>> a_dict
{'server': 'db.diveintopython3.org', 'database': 'mysql'}
'db.diveintopython3.org'
'mysql'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'db.diveintopython3.org'
```
1. 首先,通過將兩個字典項指定給 `a_dict` 變量創建了一個新字典。每個字典項都是一組鍵值對,整個字典項集合都被大括號包裹在內。
2. `'server'` 為鍵,通過 `a_dict['server']` 引用的關聯值為 `'db.diveintopython3.org'` 。
3. `'database'` 為鍵,通過 `a_dict['database']` 引用的關聯值為 `'mysql'` 。
4. 可以通過鍵獲取值,但不能通過值獲取鍵。因此 `a_dict['server']` 為 `'db.diveintopython3.org'`,而 `a_dict['db.diveintopython3.org']` 會引發例外,因為 `'db.diveintopython3.org'` 并不是鍵。
### 修改字典
字典沒有預定義的大小限制。可以隨時向字典中添加新的鍵值對,或者修改現有鍵所關聯的值。繼續前面的例子:
```
>>> a_dict
{'server': 'db.diveintopython3.org', 'database': 'mysql'}
>>> a_dict
{'server': 'db.diveintopython3.org', 'database': 'blog'}
{'server': 'db.diveintopython3.org', 'user': 'mark', 'database': 'blog'}
>>> a_dict
{'server': 'db.diveintopython3.org', 'user': 'dora', 'database': 'blog'}
>>> a_dict
{'User': 'mark', 'server': 'db.diveintopython3.org', 'user': 'dora', 'database': 'blog'}
```
1. 在字典中不允許有重復的鍵。對現有的鍵賦值將會覆蓋舊值。
2. 可隨時添加新的鍵值對。該語法與修改現有值相同。
3. 新字典項(鍵為 `'user'`,值為 `'mark'`)出現在中間。事實上,在第一個例子中字典項按順序出現是個巧合;現在它們不按順序出現同樣也是個巧合。
4. 對既有字典鍵進行賦值只會用新值替代舊值。
5. 該操作會將 `user` 鍵的值改回 "mark" 嗎?不會!仔細看看該鍵——有個大寫的 `U` 出現在 `"User"` 中。字典鍵是區分大小寫的,因此該語句創建了一組新的鍵值對,而不是覆蓋既有的字典項。對你來說它們可能是一樣的,但對于 Python 而言它們是完全不同的。
### 混合值字典
字典并非只能用于字符串。字典的值可以是任何數據類型,包括整數、布爾值、任何對象,甚至是其它的字典。而且就算在同一字典中,所有的值也無須是同一類型,您可根據需要混合匹配。字典的鍵要嚴格得多,可以是字符串、整數和其它一些類型。在同一字典中也可混合、匹配使用不同數據類型的鍵。
實際上,您已經在 [your first Python program](your-first-python-program.html#divingin) 見過一個將非字符串用作鍵的字典了。
```
SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}
```
讓我們在交互式 shell 中剖析一下:
```
>>> SUFFIXES = {1000: ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
... 1024: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']}
2
True
['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
'TB'
```
1. 類似 [列表](#lists) 和 [集合](#sets) ,`len()` 函數將返回字典中鍵的數量。
2. 而且像列表和集合一樣,可使用 `in` 運算符以測試某個特定的鍵是否在字典中。
3. `1000` _是_ 字典 `SUFFIXES` 的一個鍵;其值為一個 8 元素列表(確切地說,是 8 個字符串)。
4. 同樣, `1024` 是字典 `SUFFIXES` 的鍵;其值也是一個 8 元素列表。
5. 由于 `SUFFIXES[1000]` 是列表,可以通過它們的 0 基點索引來獲取列表中的單個元素。
### 布爾上下文環境中的字典
空字典為假值;所有其它字典為真值。
可以在 `if` 這樣的 [布爾類型上下文環境中](#booleans) 使用字典。
```
>>> def is_it_true(anything):
... if anything:
... print("yes, it's true")
... else:
... print("no, it's false")
...
no, it's false
yes, it's true
```
1. 在布爾類型上下文環境中,空字典為假值。
2. 至少包含一個鍵值對的字典為真值。
## `None`
`None` 是 Python 的一個特殊常量。它是一個 空 值。`None` 與 `False` 不同。`None` 不是 0 。`None` 不是空字符串。將 `None` 與任何非 `None` 的東西進行比較將總是返回 `False` 。
`None` 是唯一的空值。它有著自己的數據類型(`NoneType`)。可將 `None` 賦值給任何變量,但不能創建其它 `NoneType` 對象。所有值為 `None` 變量是相等的。
```
>>> type(None)
<class 'NoneType'>
>>> None == False
False
>>> None == 0
False
>>> None == ''
False
>>> None == None
True
>>> x = None
>>> x == None
True
>>> y = None
>>> x == y
True
```
### 布爾上下文環境中的 `None`
在 [布爾類型上下文環境中](#booleans), `None` 為假值,而 `not None` 為真值。
```
>>> def is_it_true(anything):
... if anything:
... print("yes, it's true")
... else:
... print("no, it's false")
...
>>> is_it_true(None)
no, it's false
>>> is_it_true(not None)
yes, it's true
```
## 深入閱讀
* [布爾運算](http://docs.python.org/3.1/library/stdtypes.html#boolean-operations-and-or-not)
* [數值類型](http://docs.python.org/3.1/library/stdtypes.html#numeric-types-int-float-long-complex)
* [序列類型](http://docs.python.org/3.1/library/stdtypes.html#sequence-types-str-unicode-list-tuple-buffer-xrange)
* [集合類型](http://docs.python.org/3.1/library/stdtypes.html#set-types-set-frozenset)
* [映射類型](http://docs.python.org/3.1/library/stdtypes.html#mapping-types-dict)
* [`fractions[分數]` 模塊](http://docs.python.org/3.1/library/fractions.html)
* [`math[數學]` 模塊](http://docs.python.org/3.1/library/math.html)
* [PEP 237: 統一長整數和整數](http://www.python.org/dev/peps/pep-0237/)
* [PEP 238: 修改除法運算符](http://www.python.org/dev/peps/pep-0238/)
您在這里: [主頁](index.html) ? [深入Python 3](table-of-contents.html#comprehensions) ?
- 版權信息
- Chapter -1 《深入 Python 3》中有何新內容
- Chapter 0 安裝 Python
- Chapter 1 你的第一個 Python 程序
- Chapter 2 內置數據類型
- Chapter 3 解析
- Chapter 4 字符串
- Chapter 5 正則表達式
- Chapter 6 閉合 與 生成器
- Chapter 7 類 & 迭代器
- Chapter 8 高級迭代器
- Chapter 9 單元測試
- Chapter 10 重構
- Chapter 11 文件
- Chapter 12 XML
- Chapter 13 序列化Python對象
- Chapter 14 HTTP Web 服務
- Chapter 15 案例研究:將chardet移植到Python 3
- Chapter 16 打包 Python 類庫
- Chapter A 使用2to3將代碼移植到Python 3
- Chapter B 特殊方法名稱
- Chapter C 接下來閱讀什么?