一簡介#
Python 是著名的 “龜叔”Guido van Rossum在 1989 年開發的。
Python 是跨平台的,它可以運行在 Windows、Mac 和各種 Linux/Unix 系統上。在 Windows 上寫 Python 程序,放到 Linux 上也是能夠運行的。
優點
1 Python 就為我們提供了非常完善的基礎代碼庫,覆蓋了網絡、文件、GUI、數據庫、文本等大量內容,被形象地稱作 “內置電池(batteries included)”。用 Python 開發,許多功能不必從零編寫,直接使用現成的即可。
2 除了內置的庫外,Python 還有大量的第三方庫,也就是別人開發的,供你直接使用的東西。
缺點
1 運行速度慢,和 C 程序相比非常慢,因為 Python 是解釋型語言,你的代碼在執行時會一行一行地翻譯成 CPU 能理解的機器碼,這個翻譯過程非常耗時,所以很慢。而 C 程序是運行前直接編譯成 CPU 能執行的機器碼,所以非常快。
2 代碼不能加密。如果要發布你的 Python 程序,實際上就是發布源代碼,這一點跟 C 語言不同,C 語言不用發布源代碼,只需要把編譯後的機器碼(也就是你在 Windows 上常見的 xxx.exe 文件)發布出去。要從機器碼反推出 C 代碼是不可能的,所以,凡是編譯型的語言,都沒有這個問題,而解釋型的語言,則必須把源碼發布出去。
python 解釋器
當我們編寫 Python 代碼時,我們得到的是一個包含 Python 代碼的以.py 為擴展名的文本文件。要運行代碼,就需要 Python 解釋器去執行.py 文件。
python 解釋器:
CPython:從 Python 官方網站下載並安裝好 Python 3.x 後,我們就直接獲得了一個官方版本的解釋器:CPython。這個解釋器是用 C 語言開發的,所以叫 CPython。在命令行下運行 python 就是啟動 CPython 解釋器。
IPython:是基於 CPython 之上的一個交互式解釋器,也就是說,IPython 只是在交互方式上有所增強,但是執行 Python 代碼的功能和 CPython 是完全一樣的。
PyPy:另一個 Python 解釋器,它的目標是執行速度。PyPy 採用 JIT 技術,對 Python 代碼進行動態編譯(注意不是解釋),所以可以顯著提高 Python 代碼的執行速度。絕大部分 Python 代碼都可以在 PyPy 下運行,但是 PyPy 和 CPython 有一些是不同的,這就導致相同的 Python 代碼在兩種解釋器下執行可能會有不同的結果。如果你的代碼要放到 PyPy 下執行,就需要了解 PyPy 和 CPython 的不同點。
Jython:運行在 Java 平台上的 Python 解釋器,可以直接把 Python 代碼編譯成 Java 字節碼執行。
IronPython:IronPython 和 Jython 類似,只不過 IronPython 是運行在微軟.Net 平台上的 Python 解釋器,可以直接把 Python 代碼編譯成.Net 的字節碼。
Python 的解釋器很多,但使用最廣泛的還是CPython。如果要和 Java 或.Net 平台交互,最好的辦法不是用 Jython 或 IronPython,而是通過網絡調用來交互,確保各程序之間的獨立性。
命令行模式和 Python 交互模式
在命令行模式下,可以執行 python 進入 Python 交互式環境,也可以執行 python hello.py 運行一個.py 文件。
執行一個.py 文件只能在命令行模式執行。敲一個命令 python hello.py。
Python 交互式環境會把每一行 Python 代碼的結果自動打印出來,但是,直接運行 Python 代碼卻不會。
Python 交互模式的代碼是輸入一行,執行一行,而命令行模式下直接運行.py 文件是一次性執行該文件內的所有代碼。可見,Python 交互模式主要是為了調試 Python 代碼用的,也便於初學者學習,它不是正式運行 Python 代碼的環境!
文本編輯器
Visual Studio Code:微軟出品、跨平台。
注意,不要用 Word 和 Windows 自帶的記事本。Word 保存的不是純文本文件,而記事本會自作聰明地在文件開始的地方加上幾個特殊字符(UTF-8 BOM),結果會導致程序運行出現莫名其妙的錯誤。
文件名只能是英文字母、數字和下劃線的組合。
直接運行 py 文件
能不能像.exe 文件那樣直接運行.py 文件呢?在 Windows 上是不行的,但是,在 Mac 和 Linux 上是可以的,方法是在.py 文件的第一行加上一個特殊的注釋:
#!/usr/bin/env python3
print('hello, world')
然後,通過命令給 hello.py 以執行權限:
$ chmod a+x hello.py
就可以直接運行 hello.py 了。
用 Python 開發程序,可以一邊在文本編輯器裡寫代碼,一邊開一個交互式命令窗口,在寫代碼的過程中,把部分代碼粘到命令行去驗證,事半功倍!
輸入和輸出
輸出:
用 print () 在括號中加上字符串,就可以向屏幕上輸出指定的文字。
print () 函數也可以接受多個字符串,用逗號 “,” 隔開,就可以連成一串輸出:
print('The quick brown fox', 'jumps over', 'the lazy dog')
print () 會依次打印每個字符串,遇到逗號 “,” 會輸出一個空格,因此,輸出的字符串是這樣拼起來的:
輸入:
input (),可以讓用戶輸入字符串,並存放到一個變量裡。比如輸入用戶的名字:
>>>name = input()
Michael
當你輸入 name = input () 並按下回車後,Python 交互式命令行就在等待你的輸入了。這時,你可以輸入任意字符,然後按回車後完成輸入。
輸入完成後,不會有任何提示,Python 交互式命令行又回到 >>> 狀態了。那我們剛才輸入的內容到哪去了?答案是存放到 name 變量裡了。可以直接輸入 name 查看變量內容:
>>> name
'Michael'
在計算機程序中,變量不僅可以為整數或浮點數,還可以是字符串,因此,name 作為一個變量就是一個字符串。
要打印出 name 變量的內容,除了直接寫 name 然後按回車外,還可以用 print () 函數:
>>> print(name)
Michael
input () 可以讓你顯示一個字符串來提示用戶
name = input('please enter your name: ')
print('hello,', name)
輸入是 Input,輸出是 Output,因此,我們把輸入輸出統稱為 Input/Output,或者簡寫為 IO。
二 python 基礎#
任何一種編程語言都有自己的一套語法,編譯器或者解釋器就是負責把符合語法的程序代碼轉換成 CPU 能夠執行的機器碼,然後執行。
Python 的語法比較簡單,採用_縮進_方式
# print absolute value of an integer:
a = 100
if a >= 0:
print(a)
else:
print(-a)
以 #開頭的語句是注釋,注釋是給人看的,可以是任意內容,解釋器會忽略掉注釋。其他每一行都是一個語句,當語句以冒號:結尾時,縮進的語句視為代碼塊。
縮進有利有弊。好處是強迫你寫出格式化的代碼,但沒有規定縮進是幾個空格還是 Tab。按照約定俗成的慣例,應該始終堅持使用4 個空格的縮進。
縮進的壞處就是 “複製-粘貼” 功能失效了,這是不方便的地方。當你重構代碼時,粘貼過去的代碼必須重新檢查縮進是否正確。此外,IDE 很難像格式化 Java 代碼那樣格式化 Python 代碼。
注意,Python 程序是大小寫敏感的,如果寫錯了大小寫,程序會報錯。
數據類型和變量#
計算機能處理的遠不止數值,還可以處理文本、圖形、音頻、視頻、網頁等各種各樣的數據,不同的數據,需要定義不同的數據類型。在 Python 中,能夠直接處理的數據類型有以下幾種:
2.1 整數
Python 可以處理任意大小的整數,當然包括負整數,在程序中的表示方法和數學上的寫法一模一樣,例如:1,100,-8080,0 等等。
計算機由於使用二進制,所以,有時候用十六進制表示整數比較方便,十六進制用 0x 前綴和 0-9,a-f 表示,例如:0xff00,0xa5b4c3d2,等等。
對於很大的數,例如 10000000000,很難數清楚 0 的個數。Python 允許在數字中間以_分隔,因此,寫成 10_000_000_000 和 10000000000 是完全一樣的。十六進制數也可以寫成 0xa1b2_c3d4。
2.2 浮點數
浮點數也就是小數,之所以稱為浮點數,是因為按照科學記數法表示時,一個浮點數的小數點位置是可變的,比如,1.23x109 和 12.3x108 是完全相等的。浮點數可以用數學寫法,如 1.23,3.14,-9.01,等等。但是對於很大或很小的浮點數,就必須用科學計數法表示,把 10 用 e 替代,1.23x109 就是 1.23e9,或者 12.3e8,0.000012 可以寫成 1.2e-5,等等。
整數和浮點數在計算機內部存儲的方式是不同的,整數運算永遠是精確的(除法難道也是精確的?是的!),而浮點數運算則可能會有四捨五入的誤差。
2.3 字符串
字符串是以單引號' 或雙引號"括起來的任意文本,比如 'abc',"xyz"等等。請注意,'' 或"" 本身只是一種表示方式,不是字符串的一部分,因此,字符串 'abc' 只有 a,b,c 這 3 個字符。如果 ' 本身也是一個字符,那就可以用 ""括起來,比如"I'm OK" 包含的字符是 I,',m,空格,O,K 這 6 個字符。
如果字符串內部既包含 ' 又包含 " 怎麼辦?可以用轉義字符\ 來標識,比如:
'I\'m \"OK\"!'
表示的字符串內容是:
I'm "OK"!
轉義字符 \ 可以轉義很多字符,比如 \n 表示換行,\t 表示制表符,字符 \ 本身也要轉義,所以 \\ 表示的字符就是 \。
如果字符串裡面有很多字符都需要轉義,就需要加很多 \,為了簡化,Python 還允許用 r'' 表示 '' 內部的字符串默認不轉義。
>>> print('\\\t\\')
\ \
>>> print(r'\\\t\\')
\\\t\\
如果字符串內部有很多換行,用 \n 寫在一行裡不好閱讀,為了簡化,Python 允許用 '''...''' 的格式表示多行內容。
>>> print('''line1
... line2
... line3''')
line1
line2
line3
上面是在交互式命令行內輸入,注意在輸入多行內容時,提示符由 >>> 變為...,提示你可以接著上一行輸入,注意... 是提示符,不是代碼的一部分。當輸入完結束符 ``` 和括號) 後,執行該語句並打印結果。
如果寫成程序並存為.py 文件,就是:
print('''line1
line2
line3''')
多行字符串 '''...''' 還可以在前面加上 r 使用
print(r'''hello,\n
world''')
hello,\n
world
2.4 布爾值
布爾值和布爾代數的表示完全一致,一個布爾值只有True、False兩種值,要麼是 True,要麼是 False,在 Python 中,可以直接用 True、False 表示布爾值(請注意大小寫),也可以通過布爾運算計算出來:
>>> True
True
>>> False
False
>>> 3 > 2
True
>>> 3 > 5
False
布爾值可以用and、or 和 not運算。
and 運算是與運算,只有所有都為 True,and 運算結果才是 True。
or 運算是或運算,只要其中有一個為 True,or 運算結果就是 True。
not 運算是非運算,它是一個單目運算符,把 True 變成 False,False 變成 True。
2.5 空值
空值是 Python 裡一個特殊的值,用None表示。None 不能理解為 0,因為 0 是有意義的,而 None 是一個特殊的空值。
此外,Python 還提供了列表、字典等多種數據類型,還允許創建自定義數據類型。
變量
變量在程序中就是用一個變量名表示了,變量名必須是大小寫英文、數字和_的組合,且不能用數字開頭。
在 Python 中,等號 = 是賦值語句,可以把任意數據類型賦值給變量,同一個變量可以反復賦值,而且可以是不同類型的變量。
這種變量本身類型不固定的語言稱之為動態語言,與之對應的是靜態語言。靜態語言在定義變量時必須指定變量類型,如果賦值的時候類型不匹配,就會報錯。例如 Java 是靜態語言,賦值語句如下(// 表示注釋):
int a = 123; // a是整數類型變量
a = "ABC"; // 錯誤:不能把字符串賦給整型變量
理解變量在計算機內存中的表示也非常重要。
a = 'ABC'
Python 解釋器幹了兩件事情:
在內存中創建了一個 'ABC' 的字符串;
在內存中創建了一個名為 a 的變量,並把它指向 'ABC'。
a = 'ABC'
b = a
a = 'XYZ'
print(b)
執行 a = 'ABC',解釋器創建了字符串 'ABC' 和變量 a,並把 a 指向 'ABC':
執行 b = a,解釋器創建了變量 b,並把 b 指向 a 指向的字符串 'ABC':
執行 a = 'XYZ',解釋器創建了字符串 'XYZ',並把 a 的指向改為 'XYZ',但 b 並沒有更改:
所以,最後打印變量 b 的結果自然是 'ABC' 了。
常量
所謂常量就是不能變的變量,比如常用的數學常數 π 就是一個常量。在 Python 中,通常用全部大寫的變量名表示常量:
PI = 3.14159265359
但事實上 PI 仍然是一個變量,Python 根本沒有任何機制保證 PI 不會被改變,所以,用全部大寫的變量名表示常量只是一個習慣上的用法,如果你一定要改變變量 PI 的值,也沒人能攔住你。
解釋一下整數的除法為什麼也是精確的。在 Python 中,有兩種除法,一種除法是 /:
>>> 10 / 3
3.3333333333333335
/ 除法計算結果是浮點數,即使是兩個整數恰好整除,結果也是浮點數:
>>> 9 / 3
3.0
還有一種除法是 //,稱為地板除,兩個整數的除法仍然是整數:
>>> 10 // 3
3
你沒有看錯,整數的地板除 // 永遠是整數,即使除不盡。要做精確的除法,使用 / 就可以。
因為 // 除法只取結果的整數部分,所以 Python 還提供一個餘數運算 %,可以得到兩個整數相除的餘數:
>>> 10 % 3
1
無論整數做 // 除法還是取餘數,結果永遠是整數,所以,整數運算結果永遠是精確的。
Python 支持多種數據類型,在計算機內部,可以把任何數據都看成一個 “對象”,而變量就是在程序中用來指向這些數據對象的,對變量賦值就是把數據和變量給關聯起來。
對變量賦值 x = y 是把變量 x 指向真正的對象,該對象是變量 y 所指向的。隨後對變量 y 的賦值不影響變量 x 的指向。
注意:Python 的整數沒有大小限制,而某些語言的整數根據其存儲長度是有大小限制的,例如 Java 對 32 位整數的範圍限制在 - 2147483648-2147483647。
Python 的浮點數也沒有大小限制,但是超出一定範圍就直接表示為 inf(無限大)。
list 和 tuple#
list
Python 內置的一種數據類型列表。list 是一種有序的集合,可以隨時添加和刪除其中的元素。
比如,列出班裡所有同學的名字,就可以用一個 list 表示:
>>> classmates = ['Michael', 'Bob', 'Tracy']
>>> classmates
['Michael', 'Bob', 'Tracy']
變量 classmates 就是一個 list。用 len () 函數可以獲得 list 元素的個數:
>>> len(classmates)
3
用索引來訪問 list 中每一個位置的元素,記得索引是從0開始。
>>> classmates[0]
'Michael'
>>> classmates[1]
'Bob'
>>> classmates[2]
'Tracy'
>>> classmates[3]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
當索引超出了範圍時,Python 會報一個IndexError錯誤,所以,要確保索引不要越界,記得最後一個元素的索引是len(classmates) - 1。
如果要取最後一個元素,除了計算索引位置外,還可以用 - 1 做索引,直接獲取最後一個元素:
>>> classmates[-1]
'Tracy'
list 是一個可變的有序表,所以,可以往 list 中追加元素到末尾:
list.append('')
>>> classmates.append('Adam')
>>> classmates
['Michael', 'Bob', 'Tracy', 'Adam']
list.insert(num,'')
也可以把元素插入到指定的位置,比如索引號為 1 的位置:
>>> classmates.insert(1, 'Jack')
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy', 'Adam']
list.pop()
要刪除 list 末尾的元素,用 pop () 方法:
>>> classmates.pop()
'Adam'
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy']
list.pop(i)
要刪除指定位置的元素,用 pop (i) 方法,其中 i 是索引位置:
>>> classmates.pop(1)
'Jack'
>>> classmates
['Michael', 'Bob', 'Tracy']
要把某個元素替換成別的元素,可以直接賦值給對應的索引位置:
>>> classmates[1] = 'Sarah'
>>> classmates
['Michael', 'Sarah', 'Tracy']
list 裡面的元素的數據類型也可以不同,比如:
>>> L = ['Apple', 123, True]
list 元素也可以是另一個 list,比如:
>>> s = ['python', 'java', ['asp', 'php'], 'scheme']
>>> len(s)
4
要注意 s 只有 4 個元素,其中 s [2] 又是一個 list,如果拆開寫就更容易理解了:
>>> p = ['asp', 'php']
>>> s = ['python', 'java', p, 'scheme']
要拿到 'php' 可以寫 p [1] 或者 s [2][1],因此 s 可以看成是一個二維數組,類似的還有三維、四維…… 數組,不過很少用到。
如果一個 list 中一個元素也沒有,就是一個空的 list,它的長度為 0:
>>> L = []
>>> len(L)
0
tuple
另一種有序列表叫元組。tuple 和 list 非常類似,但是tuple 一旦初始化就不能修改,比如同樣是列出同學的名字:
>>> classmates = ('Michael', 'Bob', 'Tracy')
現在,classmates 這個 tuple 不能變了,它也沒有 append (),insert () 這樣的方法。其他獲取元素的方法和 list 是一樣的,你可以正常地使用 classmates [0],classmates [-1],但不能賦值成另外的元素。
不可變的 tuple 有什麼意義?因為 tuple 不可變,所以代碼更安全。如果可能,能用 tuple 代替 list 就盡量用 tuple。
tuple 的陷阱:當你定義一個 tuple 時,在定義的時候,tuple 的元素就必須被確定下來,比如:
>>> t = (1, 2)
>>> t
(1, 2)
如果要定義一個空的 tuple,可以寫成 ():
>>> t = ()
>>> t
()
但是,要定義一個只有 1 個元素的 tuple,如果你這麼定義:
>>> t = (1)
>>> t
1
定義的不是 tuple,是 1 這個數!這是因為括號 () 既可以表示 tuple,又可以表示數學公式中的小括號,這就產生了歧義,因此,Python 規定,這種情況下,按小括號進行計算,計算結果自然是 1。
所以,只有 1 個元素的 tuple 定義時必須加一個逗號,來消除歧義:
>>> t = (1,)
>>> t
(1,)
Python 在顯示只有 1 個元素的 tuple 時,也會加一個逗號,以免你誤解成數學計算意義上的括號。
最後來看一個 "可變的”tuple:
>>> t = ('a', 'b', ['A', 'B'])
>>> t[2][0] = 'X'
>>> t[2][1] = 'Y'
>>> t
('a', 'b', ['X', 'Y'])
這個 tuple 定義的時候有 3 個元素,分別是 'a','b' 和一個 list。不是說 tuple 一旦定義後就不可變嗎?怎麼後來又變了?
我們先看看定義的時候 tuple 包含的 3 個元素:
當我們把 list 的元素 'A' 和 'B' 修改為 'X' 和 'Y' 後,tuple 變為:
表面上看,tuple 的元素確實變了,但其實變的不是 tuple 的元素,而是 list 的元素。tuple 一開始指向的 list 並沒有改成別的 list,所以,tuple 所謂的 “不變” 是說,tuple 的每個元素,指向永遠不變。即指向 'a',就不能改成指向 'b',指向一個 list,就不能改成指向其他對象,但指向的這個 list 本身是可變的!
理解了 “指向不變” 後,要創建一個內容也不變的 tuple 怎麼做?那就必須保證 tuple 的每一個元素本身也不能變。
list 和 tuple 是 Python 內置的有序集合,一個可變,一個不可變。
條件判斷#
據 Python 的縮進規則,如果 if 語句判斷是 True,就把縮進的兩行 print 語句執行。如果 if 判斷是 False,去把 else 執行。
注意不要少寫了冒號:
age = 3
if age >= 18:
print('your age is', age)
print('adult')
else:
print('your age is', age)
print('teenager')
elif 是 else if 的縮寫,完全可以有多個 elif。
if 語句執行特點:是從上往下判斷,如果在某個判斷上是 True,把該判斷對應的語句執行後,就忽略掉剩下的 elif 和 else。
if <條件判斷1>:
<執行1>
elif <條件判斷2>:
<執行2>
elif <條件判斷3>:
<執行3>
else:
<執行4>
if 判斷條件還可以簡寫,比如寫:
if x:
print('True')
只要 x 是非零數值、非空字符串、非空 list 等,就判斷為 True,否則為 False。
再議 input
TypeError
birth = input('birth: ')
if birth < 2000:
print('00前')
else:
print('00後')
輸入 1982,結果報錯:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: str() > int()
這是因為 input () 返回的數據類型是 str,str 不能直接和整數比較,必須先把 str 轉換成整數。Python 提供了int () 函數來完成這件事情。
int () 函數發現一個字符串並不是合法的數字時就會報錯,程序就退出了。
s = input('birth: ')
birth = int(s)
if birth < 2000:
print('00前')
else:
print('00後')
再次運行,就可以得到正確的結果。但是,如果輸入 abc 呢?又會得到一個錯誤信息:
ValueError
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'abc'
循環#
for...in 循環
Python 的循環有兩種,一種是 for...in 循環,依次把 list 或 tuple 中的每個元素迭代出來。
names = ['Michael', 'Bob', 'Tracy']
for name in names:
print(name)
執行這段代碼,會依次打印 names 的每一個元素:
Michael
Bob
Tracy
所以 for x in ... 循環就是把每個元素代入變量 x,然後執行縮進塊的語句。
range () 函數,可以生成一個整數序列,再通過 list () 函數可以轉換為 list。比如 range (5) 生成的序列是從 0 開始小於 5 的整數:
>>> list(range(5))
[0, 1, 2, 3, 4]
while 循環
第二種循環是 while 循環,只要條件滿足,就不斷循環,條件不滿足時退出循環。
break
在循環中,break 語句可以提前退出循環。
n = 1
while n <= 100:
if n > 10: # 當n = 11時,條件滿足,執行break語句
break # break語句會結束當前循環
print(n)
n = n + 1
print('END')
執行上面的代碼可以看到,打印出 1~10 後,緊接著打印 END,程序結束。
continue
在循環過程中,也可以通過 continue 語句,跳過當前的這次循環,直接開始下一次循環。
n = 0
while n < 10:
n = n + 1
if n % 2 == 0: # 如果n是偶數,執行continue語句
continue # continue語句會直接繼續下一輪循環,後續的print()語句不會執行
print(n)
break 語句可以在循環過程中直接退出循環,而 continue 語句可以提前結束本輪循環,並直接開始下一輪循環。這兩個語句通常都必須配合 if 語句使用。
要特別注意,不要濫用 break 和 continue 語句。
“死循環”,也就是永遠循環下去。這時可以用 Ctrl+C 退出程序,或者強制結束 Python 進程。
dict 和 set#
dict
Python 內置了字典:dict 的支持,dict 全稱 dictionary,在其他語言中也稱為 map,使用鍵 - 值(key-value)存儲,具有極快的查找速度。
用 dict 實現,只需要一個 “名字”-“成績” 的對照表,直接根據名字查找成績,無論這個表有多大,查找速度都不會變慢。
>>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
>>> d['Michael']
95
為什麼 dict 查找速度這麼快?因為 dict 的實現原理和查字典是一樣的。假設字典包含了 1 萬個漢字,我們要查某一個字,一個辦法是把字典從首頁往後翻,直到找到我們想要的字為止,這種方法就是在 list 中查找元素的方法,list 越大,查找越慢。
第二種方法是先在字典的索引表裡(比如部首表)查這個字對應的頁碼,然後直接翻到該頁,找到這個字。無論找哪個字,這種查找速度都非常快,不會隨著字典大小的增加而變慢。
dict 就是第二種實現方式,給定一個名字,比如 'Michael',dict 在內部就可以直接計算出 Michael 對應的存放成績的 “頁碼”,也就是 95 這個數字存放的內存地址,直接取出來,所以速度非常快。
你可以猜到,這種 key-value 存儲方式,在放進去的時候,必須根據 key 算出 value 的存放位置,這樣,取的時候才能根據 key 直接拿到 value。
把數據放入 dict 的方法,除了初始化時指定外,還可以通過 key 放入:
>>> d['Adam'] = 67
>>> d['Adam']
67
由於一個 key 只能對應一個 value,所以,多次對一個 key 放入 value,後面的值會把前面的值沖掉:
>>> d['Jack'] = 90
>>> d['Jack']
90
>>> d['Jack'] = 88
>>> d['Jack']
88
如果 key 不存在,dict 就會報錯:KeyError。
>>> d['Thomas']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'Thomas'
要避免 key 不存在的錯誤,有兩種辦法。
一是通過in判斷 key 是否存在:
>>> 'Thomas' in d
False
二是通過 dict 提供的get () 方法,如果 key 不存在,可以返回None,或者自己指定的 value:
>>> d.get('Thomas')
>>> d.get('Thomas', -1)
-1
注意:返回 None 的時候 Python 的交互環境不顯示結果。
要刪除一個 key,用pop (key) 方法,對應的 value 也會從 dict 中刪除:
>>> d.pop('Bob')
75
>>> d
{'Michael': 95, 'Tracy': 85}
請務必注意,dict 內部存放的順序和 key 放入的順序是沒有關係的。
和 list 比較,dict 有以下幾個特點:
1 查找和插入的速度極快,不會隨著 key 的增加而變慢;
2 需要占用大量的內存,內存浪費多。
而 list 相反:
1 查找和插入的時間隨著元素的增加而增加;
2 占用空間小,浪費內存很少。
所以,dict 是用空間來換取時間的一種方法。
dict 可以用在需要高速查找的很多地方,在 Python 代碼中幾乎無處不在,正確使用 dict 非常重要,需要牢記的第一條就是dict 的 key 必須是不可變對象。
這是因為dict 根據 key 來計算 value 的存儲位置,如果每次計算相同的 key 得出的結果不同,那 dict 內部就完全混亂了。這個通過 key 計算位置的算法稱為哈希算法(Hash)。
要保證 hash 的正確性,作為 key 的對象就不能變。在 Python 中,字符串、整數等都是不可變的,因此,可以放心地作為 key。而 list 是可變的,就不能作為 key:
>>> key = [1, 2, 3]
>>> d[key] = 'a list'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
set
set 和 dict 類似,也是一組 key 的集合,但不存儲 value。由於 key 不能重複,所以,在 set 中,沒有重複的 key。
要創建一個 set,需要提供一個 list 作為輸入集合:
>>> s = set([1, 2, 3])
>>> s
{1, 2, 3}
注意,傳入的參數 [1, 2, 3] 是一個 list,而顯示的 {1, 2, 3} 只是告訴你這個 set 內部有 1,2,3 這 3 個元素,顯示的順序也不表示 set 是有序的。
重複元素在 set 中自動被過濾:
>>> s = set([1, 1, 2, 2, 3, 3])
>>> s
{1, 2, 3}
通過add (key) 方法可以添加元素到 set 中,可以重複添加,但不會有效果:
>>> s.add(4)
>>> s
{1, 2, 3, 4}
>>> s.add(4)
>>> s
{1, 2, 3, 4}
通過remove (key) 方法可以刪除元素:
>>> s.remove(4)
>>> s
{1, 2, 3}
set 可以看成數學意義上的無序和無重複元素的集合,因此,兩個 set 可以做數學意義上的交集、並集等操作:
>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s1 & s2
{2, 3}
>>> s1 | s2
{1, 2, 3, 4}
set 和 dict 的唯一区别僅在於沒有存儲對應的 value,但是,set 的原理和 dict 一樣,所以,同樣不可以放入可變對象,因為無法判斷兩個可變對象是否相等,也就無法保證 set 內部 “不會有重複元素”。
再議不可變對象
對於可變對象,比如 list,對 list 進行操作,list 內部的內容是會變化的,比如:
>>> a = ['c', 'b', 'a']
>>> a.sort()
>>> a
['a', 'b', 'c']
而對於不可變對象,比如 str,對 str 進行操作呢:
>>> a = 'abc'
>>> a.replace('a', 'A')
'Abc'
>>> a
'abc'
雖然字符串有個 replace () 方法,也確實變出了 'Abc',但變量 a 最後仍是 'abc',應該怎麼理解呢?
我們先把代碼改成下面這樣:
>>> a = 'abc'
>>> b = a.replace('a', 'A')
>>> b
'Abc'
>>> a
'abc'
要始終牢記的是,a 是變量,而 'abc' 才是字符串對象!有些時候,我們經常說,對象 a 的內容是 'abc',但其實是指,a 本身是一個變量,它指向的對象的內容才是 'abc':
a-------->'abc'
當我們調用 a.replace ('a', 'A') 時,實際上調用方法 replace 是作用在字符串對象 'abc' 上的,而這個方法雖然名字叫 replace,但卻沒有改變字符串 'abc' 的內容。相反,replace 方法創建了一個新字符串 'Abc' 並返回,如果我們用變量 b 指向該新字符串,就容易理解了,變量 a 仍指向原有的字符串 'abc',但變量 b 卻指向新字符串 'Abc' 了:
a-------->'abc'
b-------->'Abc'
所以,對於不變對象來說,調用對象自身的任意方法,也不會改變該對象自身的內容。相反,這些方法會創建新的對象並返回,這樣,就保證了不可變對象本身永遠是不可變的。
使用 key-value 存儲結構的 dict 在 Python 中非常有用,選擇不可變對象作為 key 很重要,最常用的 key 是字符串。