字典(dictionary)
我們都曾經使用過語言詞典來查詢不認識的單詞的定義。語言詞典針對給定的單詞(比如 python)提供一組標準的資訊。這種系統將定義和其他資訊與實際的單詞關聯(對映)起來。使用單詞作為鍵定位器來尋找感興趣的資訊。這種概念延伸到 Python 程式語言中,就成了特殊的容器型別,稱為 字典(dictionary)。
字典(dictionary) 資料型別在許多語言中都存在。它有時候稱為關聯 陣列(因為資料與一個鍵值相關聯),或者作為雜湊表。但是在 Python 中,字典(dictionary) 是一個很好的物件,因此即使是程式設計新手也很容易在自己的程式中使用它。按照正式的說法,Python 中的 字典(dictionary) 是一種異構的、易變的對映容器資料型別。
建立字典
本系列中前面的文章介紹了 Python 程式語言中的一些容器資料型別,包括 tuple、string 和 list(參見 參考資料)。這些容器的相似之處是它們都是基於序列的。這意味著要根據元素在序列中的位置訪問這些集合中的元素。所以,給定一個名為 a 的序列,就可以使用數字索引(比如 a[0] )或片段(比如 a[1:5])來訪問元素。Python 中的 字典(dictionary) 容器型別與這三種容器型別的不同之處在於,它是一個無序的集合。不是按照索引號,而是使用鍵值來訪問集合中的元素。這意味著構造字典(dictionary)容器比 tuple、string 或 list 要複雜一些,因為必須同時提供鍵和相應的值,如清單 1 所示。
清單 1. 在 Python 中建立字典,第 1 部分
>>> d = {0: 'zero', 1: 'one', 2 : 'two', 3 : 'three', 4 : 'four', 5: 'five'} >>> d {0: 'zero', 1: 'one', 2: 'two', 3: 'three', 4: 'four', 5: 'five'} >>> len(d) >>> type(d) # Base object is the dict class <type 'dict'> >>> d = {} # Create an empty dictionary >>> len(d) >>> d = {1 : 'one'} # Create a single item dictionary >>> d {1: 'one'} >>> len(d) >>> d = {'one' : 1} # The key value can be non-numeric >>> d {'one': 1} >>> d = {'one': [0, 1,2 , 3, 4, 5, 6, 7, 8, 9]} >>> d {'one': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]}
如這個例子所示,在 Python 中建立字典(dictionary)要使用花括號和以冒號分隔的鍵-值組合。如果沒有提供鍵-值組合,那麼就會建立一個空的 dictionary。使用一個鍵-值組合,就會建立具有一個元素的 dictionary,以此類推,直至您需要的任何規模。與任何容器型別一樣,可以使用內建的 len 方法查明集合中元素的數量。
前面的示例還演示了關於字典(dictionary)容器的另一個重要問題。鍵並不限制為整數;它可以是任何不易變的資料型別,包括 integer、float、tuple 或 string。因為 list 是易變的,所以它不能作為字典(dictionary)中的鍵。但是字典(dictionary)中的值可以是任何資料型別的。
最後,這個示例說明了 Python 中字典(dictionary)的底層資料型別是 dict 物件。要進一步瞭解如何使用 Python 中的 字典(dictionary),可以使用內建的幫助直譯器來了解 dict 類,如清單 2 所示。
清單 2. 獲得關於字典(dictionary)的幫助
>>> help(dict)on class dict in module __builtin__: dict(object) | dict() -> new empty dictionary. | dict(mapping) -> new dictionary initialized from a mapping object's | (key, value) pairs. | dict(seq) -> new dictionary initialized as if via: | d = {} | for k, v in seq: | d[k] = v | dict(**kwargs) -> new dictionary initialized with the name=value pairs | in the keyword argument list. For example: dict(one=1, two=2) | | Methods defined here: | | __cmp__(...) | x.__cmp__(y) <==> cmp(x,y) | | __contains__(...) | x.__contains__(y) <==> y in x | | __delitem__(...) | x.__delitem__(y) <==> del x[y] ...
關於 dict 類的幫助指出,可以使用建構函式直接建立字典(dictionary),而不使用花括號。既然與其他容器資料型別相比,在建立字典(dictionary)時必須提供更多的資料,那麼這些建立方法比較複雜也就不足為奇了。但是,在實踐中使用字典(dictionary)並不難,如清單 3 所示。
清單 3. 在 Python 中建立字典(dictionary),第 2 部分
>>> l = [0, 1,2 , 3, 4, 5, 6, 7, 8, 9] >>> d = dict(l)(most recent call last): File "<stdin>", line 1, in ?: can't convert dictionary update sequence element #0 to a sequence >>> l = [(0, 'zero'), (1, 'one'), (2, 'two'), (3, 'three')] >>> d = dict(l) >>> d {0: 'zero', 1: 'one', 2: 'two', 3: 'three'} >>> l = [[0, 'zero'], [1, 'one'], [2, 'two'], [3, 'three']] >>> d {0: 'zero', 1: 'one', 2: 'two', 3: 'three'} >>> d = dict(l) >>> d {0: 'zero', 1: 'one', 2: 'two', 3: 'three'} >>> d = dict(zero=0, one=1, two=2, three=3) >>> d {'zero': 0, 'three': 3, 'two': 2, 'one': 1} >>> d = dict(0=zero, 1=one, 2=two, 3=three): keyword can't be an expression
可以看到,建立字典(dictionary)需要鍵值和資料值。第一次從 list 建立字典(dictionary)的嘗試失敗了,這是因為沒有匹配的鍵-資料值對。第二個和第三個示例演示瞭如何正確地建立 字典(dictionary):在第一種情況下,使用一個 list,其中的每個元素都是一個 tuple;在第二種情況下,也使用一個 list,但是其中的每個元素是另一個 list。在這兩種情況下,內層容器都用於獲得鍵到資料值的對映。
直接建立 dict 容器的另一個方法是直接提供鍵到資料值的對映。這種技術允許顯式地定義鍵和與其對應的值。這個方法其實用處不大,因為可以使用花括號完成相同的任務。另外,如前面的例子所示,在採用這種方式時對於鍵不能使用數字,否則會導致丟擲一個異常。
訪問和修改字典(dictionary)
建立了 dictionary 之後,需要訪問其中包含的資料。訪問方式與訪問任何 Python 容器資料型別中的資料相似,如清單 4 所示。
清單 4. 訪問 dictionary 中的元素
>>> d = dict(zero=0, one=1, two=2, three=3) >>> d {'zero': 0, 'three': 3, 'two': 2, 'one': 1} >>> d['zero'] >>> d['three'] >>> d = {0: 'zero', 1: 'one', 2 : 'two', 3 : 'three', 4 : 'four', 5: 'five'} >>> d[0] 'zero' >>> d[4] 'four' >>> d[6](most recent call last): File "<stdin>", line 1, in ?: 6 >>> d[:-1](most recent call last): File "<stdin>", line 1, in ?: unhashable type
可以看到,從字典(dictionary)中獲取資料值的過程幾乎與從任何容器型別中獲取資料完全一樣。在容器名後面的方括號中放上鍵值。當然,字典(dictionary)可以具有非數字的鍵值,如果您以前沒有使用過這種資料型別,那麼適應這一點需要些時間。因為在字典(dictionary)中次序是不重要的(dictionary 中資料的次序是任意的),所以可以對其他容器資料型別使用的片段功能,對於 字典(dictionary)是不可用的。試圖使用片段或者試圖從不存在的鍵訪問資料就會丟擲異常,指出相關的錯誤。
Python 中的字典(dictionary)容器也是易變的資料型別,這意味著在建立它之後可以修改它。如清單 5 所示,可以新增新的鍵到資料值的對映,可以修改現有的對映,還可以刪除對映。
清單 5. 修改字典(dictionary)
>>> d = {0: 'zero', 1: 'one', 2: 'two', 3: 'three'} >>> d[0] 'zero' >>> d[0] = 'Zero' >>> d {0: 'Zero', 1: 'one', 2: 'two', 3: 'three'} >>> d[4] = 'four' >>> d[5] = 'five' >>> d {0: 'Zero', 1: 'one', 2: 'two', 3: 'three', 4: 'four', 5: 'five'} >>> del d[0] >>> d {1: 'one', 2: 'two', 3: 'three', 4: 'four', 5: 'five'} >>> d[0] = 'zero' >>> d {0: 'zero', 1: 'one', 2: 'two', 3: 'three', 4: 'four', 5: 'five'}
清單 5 演示了幾個重點。首先,修改資料值是很簡單的:將新的值分配給適當的鍵。其次,新增新的鍵到資料值的對映也很簡單:將相關資料分配給新的鍵值。Python 自動進行所有處理。不需要呼叫 append 這樣的特殊方法。對於 dictionary 容器,次序是不重要的,所以這應該好理解,因為不是在字典(dictionary)後面附加對映,而是將它新增到容器中。最後,刪除對映的辦法是使用 del 運算子以及應該從容器中刪除的鍵。
在清單 5 中有一個情況看起來有點兒怪,鍵值是按照數字次序顯示的,而且這個次序與插入對映的次序相同。不要誤解 —— 情況不總是這樣的。Python 字典(dictionary)中對映的次序是任意的,對於不同的 Python 安裝可能會有變化,甚至多次使用同一 Python 直譯器執行相同程式碼也會有變化。如果在一個字典(dictionary)中使用不同型別的鍵和資料值,那麼就很容易看出這一點,如清單 6 所示。
清單 6. 異構的容器
>>> d = {0: 'zero', 'one': 1} >>> d {0: 'zero', 'one': 1} >>> d[0] 'zero' >>> type(d[0]) <type 'str'> >>> d['one'] >>> type(d['one']) <type 'int'> >>> d['two'] = [0, 1, 2] >>> d {0: 'zero', 'two': [0, 1, 2], 'one': 1} >>> d[3] = (0, 1, 2, 3) >>> d {0: 'zero', 3: (0, 1, 2, 3), 'two': [0, 1, 2], 'one': 1} >>> d[3] = 'a tuple' >>> d {0: 'zero', 3: 'a tuple', 'two': [0, 1, 2], 'one': 1}
如這個例子所示,可以在一個字典(dictionary)中使用不同資料型別的鍵和資料值。還可以透過修改字典(dictionary)新增新的型別。最後,產生的 dictionary 的次序並不與插入資料的次序匹配。本質上,字典(dictionary)中元素的次序是由 Python 字典(dictionary)資料型別的實際實現控制的。新的 Python 直譯器很容易改變這一次序,所以一定不要依賴於元素在字典(dictionary)中的特定次序。
用字典(dictionary)進行程式設計
作為正式的 Python 資料型別,字典(dictionary)支援其他較簡單資料型別所支援的大多數操作。這些操作包括一般的關係運算子,比如 <、> 和 ==,如清單 7 所示。
清單 7. 一般關係運算子
>>> d1 = {0: 'zero'} >>> d2 = {'zero':0} >>> d1 < d2 >>> d2 = d1 >>> d1 < d2 >>> d1 == d2 >>> id(d1) >>> id(d2) >>> d2 = d1.copy() >>> d1 == d2 >>> id(d1) >>> id(d2)
前面的示例建立兩個字典(dictionary)並使用它們測試 < 關係運算子。儘管很少以這種方式比較兩個字典(dictionary);但是如果需要,可以這樣做。
然後,這個示例將賦值給變數 d1 的字典(dictionary)賦值給另一個變數 d2。注意,內建的 id() 方法對於 d1 和 d2 返回相同的識別符號值,這說明這不是複製操作。要想複製字典(dictionary) ,可以使用 copy() 方法。從這個示例中的最後幾行可以看出,副本與原來的字典(dictionary)完全相同,但是容納這字典(dictionary)的變數具有不同的識別符號。
在 Python 程式中使用字典(dictionary)時,很可能希望檢查字典(dictionary)中是否包含特定的鍵或值。如清單 8 所示,這些檢查很容易執行。
清單 8. 條件測試和字典(dictionary)
>>> d = {0: 'zero', 3: 'a tuple', 'two': [0, 1, 2], 'one': 1} >>> d.keys() [0, 3, 'two', 'one'] >>> if 0 in d.keys(): ... print 'True' ... >>> if 'one' in d: ... print 'True' ... >>> if 'four' in d: ... print 'Dictionary contains four' ... elif 'two' in d: ... print 'Dictionary contains two' ... contains two
測試字典(dictionary)中鍵或資料值的成員關係是很簡單的。dictionary 容器資料型別提供幾個內建方法,包括 keys() 方法和 values() 方法(這裡沒有演示)。這些方法返回一個列表,其中分別包含進行呼叫的字典(dictionary)中的鍵或資料值。
因此,要判斷某個值是否是字典(dictionary)中的鍵,應該使用 in 運算子檢查這個值是否在呼叫 keys() 方法所返回的鍵值列表中。可以使用相似的操作檢查某個值是否在呼叫 values() 方法所返回的資料值列表中。但是,可以使用字典(dictionary)名作為簡寫表示法。這是有意義的,因為一般希望知道某個資料值(而不是鍵值)是否在字典(dictionary)中。
在 “Discover Python, Part 6” 中,您看到了使用 for 迴圈遍歷容器中的元素是多麼容易。同樣的技術也適用於 Python 字典(dictionary),如清單 9 所示。
清單 9. 迭代和字典(dictionary)
>>> d = {0: 'zero', 3: 'a tuple', 'two': [0, 1, 2], 'one': 1} >>> for k in d.iterkeys(): ... print d[k] ... tuple [0, 1, 2] >>> for v in d.itervalues(): ... print v ... tuple [0, 1, 2] >>> for k, v in d.iteritems(): ... print 'd[',k,'] = ',v ... [ 0 ] = zero[ 3 ] = a tuple[ two ] = [0, 1, 2][ one ] = 1
這個示例演示了遍歷字典(dictionary)的三種方式:使用從 iterkeys()、itervalues() 或 iteritems() 方法返回的 Python 迭代器。(順便說一下,可以透過在字典(dictionary)上直接呼叫適當方法,比如 d.iterkeys(),從而檢查這些方法是否返回一個迭代器而不是容器資料型別。)iterkeys() 方法允許遍歷字典(dictionary)的鍵,而 itervalues() 方法允許遍歷字典(dictionary)包含的資料值。另一方面,iteritems() 方法允許同時遍歷鍵到資料值的對映。
字典(dictionary):另一種強大的 Python 容器
本文討論了 Python 字典(dictionary)資料型別。字典(dictionary)是一種異構的、易變的容器,依賴鍵到資料值的對映(而不是特定的數字次序)來訪問容器中的元素。訪問、新增和刪除字典(dictionary)中的元素都很簡單,而且字典(dictionary)很容易用於複合語句,比如 if 語句或 for 迴圈。可以在字典(dictionary)中儲存所有不同型別的資料,可以按照名稱或其他複合鍵值(比如 tuple)訪問這些資料,所以 Python 字典(dictionary)使開發人員能夠編寫簡潔而又強大的程式設計語句。