第六章:數字
第七章:序列,列表,元組
1.序列
序列類型有著相同的訪問模式:它的每一個元素可以通過指定一個偏移量的方式得到。而
多個元素可以通過切片操作的方式一次得到
標準類型操作符
>, < .== 值比較
is ,is not 身份比較
and or 布爾邏輯運算符
序列類型操作符
- 成員關機操作符
in ,not in - 序列類型操作符
序列操作符 --------作用
seq[ind] --------獲得下標為 ind 的元素
seq[ind1:ind2]----獲得下標從 ind1 到 ind2 間的元素集合
seq * expr --------序列重復 expr 次
seq1 + seq2 ------連接序列 seq1 和 seq2
obj in seq --------判斷 obj 元素是否包含在 seq 中
obj not in seq ----判斷 obj 元素是否不包含在 seq 中
- 連接接操作符(+)
把一個序列和另一個相同類型的序列做連接
這個操作不是最快或者說最有效的。對字符
串來說,這個操作不如把所有的==子字符串放到一個列表或可迭代對象中==,然后調用一個 join
方法來把所有的內容連接在一起節(jié)約內存;類似地,對列表來說,我們推薦讀者用==列表類型的
extend()方法來把兩個或者多個列表對象合并==.當你需要簡單地把兩個對象的內容合并, 或者說
不能依賴于可變對象的那些沒有返回值(實際上它返回一個 None)的內建方法來完成的時候時,
連接操作符還是很方便的一個選擇。
a = 'abc'
b = 'cde'
c = ''.join([a,b])
- 重復操作符 ( * )
當你需要需要一個序列的多份拷貝時,重復操作符非常有用,
- 切片操作符 ( [], [:], [::] )
簡單地講, 所謂序列類型就是包含一些順序排列的對象的一個結構.你可以簡單的用方括號
加一個下標的方式訪問它的每一個元素,或者通過在方括號中用冒號把開始下標和結束下標分
開的方式來訪問一組連續(xù)的元素.下面我們將詳細的講解提到的這兩種方式.==序列類型是其元素
被順序放置的一種數據結構類型==
切片操作的三種形式:
s = 'abcdefghigklmn'
a = s[:2] ab
b = s[0:2] ab
c = s[2:] cdefghigklmn
c = s[0:10:3] adgg
內建函數(BIFS)
- 類型轉換
==轉換實際上是工廠函數,將對象作為參數,并將其內容(淺)拷貝到新生成的對象中==
函數 含義
list(iter) -------把可迭代對象轉換為列表
str(obj) -------把 obj
對象轉換成字符串(對象的字符串表示法)
unicode(obj) --把對象轉換成 Unicode 字符串(使用默認編碼)
basestring() ----抽象工廠函數,其作用僅僅是為 str 和 unicode 函數提供父類,所以不能被
實例化,也不能被調用(詳見第 6.2 節(jié))
tuple(iter) -------把一個可迭代對象轉換成一個元組對象
為什么 Python 里面不簡單地把一個對象轉換成另
一個對象呢?
回過頭看一下第 4 章就會知道,一旦一個 Python 的對象被建立,我們就不能更改
其身份或類型了.如果你把一個列表對象傳給 list()函數,便會創(chuàng)建這個對象的一個淺拷貝,
然后將其插入新的列表中。同樣地,在做連接操作和重復操作時,我們也會這樣處理
python 內建對象匯總
==函數名== 功能
enumerate(iter) a 接受一個可迭代對象作為參數,返回一個 enumerate 對象(同時也是一個迭代器),該對象生成由 iter 每個元素的index 值和 item 值組成的元組(PEP 279)
len(seq) 返回 seq 的長度
max(iter,key=None) or
max(arg0,arg1...,key=None)b 返回 iter 或(arg0,arg1,...)中的最大值,如果指定了 key,這個 key 必須是一個可以傳給 sort()方法的,用于比較的回調函數.
min(iter, key=None) or
min(arg0, arg1.... key=None)b 返回 iter 里面的最小值;或者返回(arg0,arg2,...)里面的最小值;如果指定了 key,這個 key 必須是一個可以傳給sort()方法的,用于比較的回調函數.
reversed(seq)c 接受一個序列作為參數,返回一個以逆序訪問的迭代器(PEP 322)
sorted(iter,func=None,key=None,reverse=False)c 接受一個可迭代對象作為參數,返回一個有序的列表;可選參數func,key 和 reverse 的含義跟 list.sort()內建函數的參數含義一樣.
sum(seq, init=0)a 返 回 seq 和 可 選 參 數 init 的 總 和 , 其 效 果 等 同 于reduce(operator.add,seq,init)
zip([it0, it1,... itN])d 返回一個列表,其第一個元素是it0,it1,...這些元素的第一個元素組成的一個元組,第二個...,類推.
實例:
lambda 函數(自帶return)
f = lambda x: x + 1
f(2)
filter(func, list)
接受兩個參數:一個函數func和一個列表list,返回一個列表。函數func只能有一個參數。列表中所有元素作為參數傳遞給函數,返回可以另func返回真的元素的列表
a = filter(lambda x: str(x).startswith('a'),['aaa','abd','bdc','rgt'])
print(list(a))
zip(iter1,iter2,iter3,...)
zip函數接受任意多個序列作為參數,將所有序列按相同的索引組合成一個元素是各個序列合并成的tuple的新序列,新的序列的長度以參數中最短的序列為準。另外(*)操作符與zip函數配合可以實現與zip相反的功能,即將合并的序列拆成多個tuple
a = zip([1,2,3], ['a', 'b', 'c'])
print(list(a)) [(1, 'a'), (2, 'b'), (3, 'c')]
map(func,iter1)
為list中每個對象執(zhí)行func,綁定的函數為修改list中每一個值的函數
a = map(lambda x:x*2,[1,2,3,4]) #<map object at 0x101ed4710>
print(list(a)) [2, 4, 6, 8]
2.字符串
跟數字類型一樣,字符串類型也是不可變的,所以你要改變一個字符串就必須通過創(chuàng)建一
個新串的方式來實現。也就是說你不能只改變一個字符串的一個字符或者一個子串,然而,通
過拼湊一個舊串的各個部分來得到一個新串是被允許的
3.字符串和操作符
字符串索引的時候,直接使用超過字符串長度的索引值會報錯,但是在切片的時候,如果切片的值超過了字符串的長度,不會報錯,表示從開始只到末尾值
a = 'abcd'
a[2:4] #'cd'
a[4] #IndexError: string index out of range
在進行反向索引操作時,是從-1 開始,向字符串的開始方向計數,到字符串長度的負數為
索引的結束
成員操作符(in ,not in) ##判斷字符
成員操作符用于判斷一個==字符==或者一個==子串中的字符==是否出現在另一個字符串中。出現
則返回 True,否則返回 False.注意,成員操作符不是用來==判斷一個字符串是否包含另一個字符
串的,這樣的功能由 find()或者 index()(還有它們的兄弟:rfind()和 rindex())函數來完成==
字符串連接(+)
- 運行時字符串連接
- 普通字符串轉化為 Unicode 字符串
如果把一個普通字符串和一個 Unicode 字符串做連接處理,Python 會在連接操作前先把普
通字符串轉化為 Unicode 字符串
'Hello' + u' ' + 'World' + u'!' # u'Hello World!'
- 編譯時字符串連接
只適用于字符串的操作符
- 格式化操作符( % )
Python 支持兩種格式的輸入參數。第一種是元組(見 2.8 節(jié),6.15 節(jié)),這基本上是一種的 C
printf()風格的轉換參數集;
Python 支持的第二種形式是字典形式(詳見第 7 章).字典其實是一個哈希鍵-值對的集合。
這種形式里面,key 是作為格式字符串出現,相對應的 value 值作為參數在進行轉化時提
供給格式字符串
字符串模版
原始字符串操作符(r/R)
關于原始字符串的目的,在 Python1.5 里面已經有說明,是為了對付那些在字符串中出現
的特殊字符(下面的小節(jié)會介紹這些特殊字符)。在原始字符串里,所有的字符都是直接按照字
面的意思來使用,沒有轉義特殊或不能打印的字符。
- Unicode 字符串操作符( u/U )
Unocide 字符串操作符,大寫的(U)和小寫的(u)是在 Python1.6 中 和 Unicode 字符串一
起被引入的. 它用來把標準字符串或者是包含 Unicode 字符的字符串轉換成完全地 Unicode 字
符串對象。
內建函數
- 標準類型函數
- 序列類型函數
- 字符串類型函數
raw_input()
內建的 raw_input()函數使用給定字符串提示用戶輸入并將這個輸入返回,
str() and unicode()
str()和 unicode()函數都是工廠函數,就是說產生所對應的類型的對象.它們接受一個任
意類型的對象, 然后創(chuàng)建該對象的可打印的或者 Unicode 的字符串表示.
chr(), unichr(), and ord()
chr()函數用一個范圍在 range(256)內的(就是 0 到 255)整數做參數,返回一個對應的字
符.unichr()跟它一樣,只不過返回的是 Unicode 字符,這個從 Python2.0 才加入的 unichr()
的參數范圍依賴于你的 Python 是如何被編譯的.如果是配置為 USC2 的 Unicode,那么它的允許
范 圍 就 是 range(65536) 或 者 說 0x0000-0xFFFF; 如 果 配 置 為 UCS4 , 那 么 這 個 值 應 該 是
range(1114112)或者 0x000000-0x110000.如果提供的參數不在允許的范圍內,則會報一個
ValueError 的異常。
ord()函數是 chr()函數(對于 8 位的 ASCII 字符串)或 unichr()函數(對于 Unicode 對象)
的配對函數,它以一個字符(長度為 1 的字符串)作為參數,返回對應的 ASCII 數值,或者 Unicode
數值,如果所給的 Unicode 字符超出了你的 Python 定義范圍,則會引發(fā)一個 TypeError 的異常
字符串內建函數
string.decode(encoding='UTF-8',errors='strict') 以 encoding 指定的編碼格式解碼 string,如果出錯默認報一個ValueError 的 異 常 , 除 非 errors 指 定的 是 'ignore' 或 者'replace'
string.encode(encoding='UTF-8',errors='strict') 以 encoding指定的編碼格式編碼 string,如果出錯默認報一個ValueError的異常,除非errors指定的是'ignore'或者'replace
字符串的獨特特性
- 特殊字符串和控制字符
像其他高級語言和腳本語言一樣, 一個反斜線加一個單一字符可以表示一個特殊字符,通常
是一個不可打印的字符,這就是我們上面討論的特殊字符,如果這些特殊字符是包含在一個原
始字符串中的,那么它就失去了轉義的功能
- 三引號
- 字符串不變性
每次你修改一個字符串或者做一些改變字符串內容的操作時,Python 都會自動為你分配
一個新串
Unicode
從 Python1.6 起引進的Unicode字符串支持,是用來在多種雙字節(jié)字符的格式、 編碼進行轉換的,其中包括一些對這類字符串的操作管理功能。 內建的字符串和正則表達式對 Unicode 字符串的支持,再加上 string 模塊的輔助,Python 已經可以應付大部分應用對 Unicode 的存儲、訪問、操作的需要了。
Unicode 通過使用一個或多個字節(jié)來表示一個字符的方法突破了 ASCII 的限制. 在這樣機
制下, Unicode 可以表示超過 90,000 個字符
為了讓 Unicode 和 ASCII 碼值的字符串看起來盡可能的相像,Python 的字符串從原來的簡
單數據類型改成了真正的對象.ASCII 字符串成了 StringType,而 Unicode 字符串成了
UnicodeType 類型.它們的行為是非常相近的.string 模塊里面都有相應的處理函數.string 模
塊已經停止了更新,只保留了 ASCII 碼的支持,string 模塊已經不推薦使用,在任何需要跟
Unicode 兼容的代碼里都不要再用該模塊,Python 保留該模塊僅僅是為了向后兼容
Python 里面處理 Unicode 字符串跟處理 ASCII 字符串沒什么兩樣.Python 把硬編碼的字符
串叫做字面上的字符串,默認所有字面上的字符串都用 ASCII 編碼, 可以通過在字符串前面加一
個'u'前綴的方式聲明 Unicode 字符串,這個'u'前綴告訴 Python 后面的字符串要編碼成 Unicode
字符串
內建的 str()函數和 chr()函數并沒有升級成可以處理 Unicode.它們只能處理常規(guī)的
ASCII 編碼字符串==如果一個 Unicode 字符串被作作為參數傳給了 str()函數,它會首先被轉換
成 ASCII 字符串然后在交給 str()函數==.如果該 Unicode 字符串中包含任何不被 ASCII 字符串支
持的字符,會導致 str()函數報異常.同樣地,chr()函數只能以 0 到 255 作為參數工作.如果你
傳給它一個超出此范圍的值(比如說一個 Unicode 字符),它會報異常.
==新的內建函數 unicode()和 unichar()可以看成 Unicode 版本的 str()和 chr().Unicode()
函數可以把任何 Python 的數據類型轉換成一個 Unicode 字符串,如果是對象,并且該對象定義
了unicode()方法,它還可以把該對象轉換成相應的 Unicode 字符串==.
codec 是 COder/DECoder 的首字母組合.它定義了文本跟二進制值的轉換方式,跟 ASCII 那
種用一個字節(jié)把字符轉換成數字的方式不同,Unicode 用的是多字節(jié).這導致了 Unicode 支持多
種 不 同 的 編 碼 方 式 . 比 如 說 codec 支 持 的 四 種 耳 熟 能 詳 的 編 碼 方 式 是 :ASCII,ISO
8859-1/Latin-1,UTF-8 和 UTF-16.
中最著名的是 UTF-8 編碼,它也用一個字節(jié)來編碼 ASCII 字符,這讓那些必須同時處理 ASCII
碼和 Unicode 碼文本的程序員的工作變得非常輕松,因為 ASCII 字符的 UTF-8 編碼跟 ASCII 編
碼完全相同。
UTF-8 編碼可以用 1 個到 4 個字節(jié)來表示其他語言的字符,CJK/East 這樣的東亞文字一般都
是用 3 個字節(jié)來表示,那些少用的、特殊的、或者歷史遺留的字符用 4 個字節(jié)來表示.這給那些
需要直接處理 Unicode 數據的程序員帶來了麻煩,因為他們沒有辦法按照固定長度逐一讀出各
個字符.幸運的是我們不需要掌握直接讀寫 Unicode 數據的方法,Python 已經替我們完成了相
關細節(jié),我們無須為處理多字節(jié)字符的復雜問題而擔心.Python 里面的其他編碼不是很常用,
事實上,我們認為大部分的 Python 程序員根本就用不著去處理其他的編碼,UTF-16 可能是個
例外.
UTF-16 可能是以后大行其道的一種編碼格式,它容易讀寫,因為它把所有的字符都是用單
獨的一個 16 位字,兩個字節(jié)來存儲的,正因為此,這兩個字節(jié)的順序需要定義一下,一般的
UTF-16 編碼文件都需要一個 BOM(Byte Order Mark),或者你顯式地定義 UTF-16-LE(小端)或
者 UTF-16-BE(大端)字節(jié)序.
從技術上講,UTF-16 也是一種變長編碼,但它不是很常用(人們一般不會知道或者根本不
在意除了基本多文種平面 BMP 之外到底使用的是那種平面),盡管如此,UTF-16 并不向后兼容
ASCII,因此,實現它的程序很少,因為大家需要對 ASCII 進行支持
Unicode實際應用
程序中出現字符串時一定要加個前綴 u.
- 不要用 str()函數,用 unicode()代替.
- 不要用過時的 string 模塊 -- 如果傳給它的是非 ASCII 字符,它會把一切搞砸。
- 不到必須時不要在你的程序里面編解碼 Unicod 字符.只在你要寫入文件或數據庫或者
網絡時,才調用 encode()函數;相應地,只在你需要把數據讀回來的時候才調用 decode()
函數.
字符串關鍵點總結
列表
- 標準類型函數
- 序列類型函數
list(),tuple()
list()函數和 tuple()函數接受可迭代對象(比如另一個序列)作為參數,并通過淺拷貝數據
來創(chuàng)建一個新的列表或者元組.雖然字符串也是序列類型的,但是它們并不是經常用于 list()和
tuple(). 1
無論 list()還是 tuple()都不可能做完全的轉換(見
6.1.2 節(jié)).也就是說,你傳給 tuple()的一個列表對象不可能變成一個元組,而你傳給 list()的
對象也不可能真正的變成一個列表.雖然前后兩個對象(原來的和新的對象)有著相同的數據集
合(所以相等 == ),但是變量指向的卻不是同一個對象了(所以執(zhí)行 is 操作會返回 false).還
要注意,即使它們的所有的值都相同,一個列表也不可能"等于"一個元組
- 列表類型內建函數
那些可以改變對象值的可變對象的方法是沒有返回值的!
Python 初學者經常會陷入一個誤區(qū):調用一個方法就返回一個值.最明顯的例子就是
sort():
>>> music_media.sort()# 沒有輸出?
>>>
在使用可變對象的方法如 sort(),extend()和 reverse()的時候要注意,這些操作會在列表
中原地執(zhí)行操作,也就是說現有的列表內容會被改變,但是沒有返回值!是的,與之相反,字符串,方法確實有返回值:
>>> 'leanna, silly girl!'.upper()
'LEANNA, SILLY GIRL!'
溫習一下,字符串是不可變的 -- 不可變對象的方法是不能改變它們的值的,所以它們必須
返回一個新的對象.如果你確實需要返回一個對象,那么我們建議你看一下 Python2.4 以后加入的 reversed()和 sorted()內建函數.
它們像列表的方法一樣工作,不同的是它們可以用做表達式,因為它們返回一對象.同時原來的那個列表還是那個列表,沒有改變,而你得到的是一個新的對象.
列表的特殊特性(用列表構建其他數據結構)
堆棧
元組
元組是一種
不可變類型.正因為這個原因,元組能做一些列表不能做的事情... 用做一個字典的 key.另外當
處理一組對象時,這個組默認是元組類型.
元組的特殊特性
不可變性給元組帶來了什么影響?
在三個標準不可變類型里面--數字, 字符串和元組字符串--元組是受到影響最大的,一個數
據類型是不可變的,簡單來講,就意味著一旦一個對象被定義了,它的值就不能再被更新,除非重
新創(chuàng)建一個新的對象.對數字和字符串的影響不是很大,因為它們是標量類型,當它們代表的值
改變時,這種結果是有意義的,是按照你所想要的方式進行訪問的,而對于元組,事情就不是
這樣了。
因為元組是容器對象,很多時候你想改變的只是這個容器中的一個或者多個元素,不幸的
是這是不可能的,切片操作符不能用作左值進行賦值。這和字符串沒什么不同,切片操作只能
用于只讀的操作。
元組也不是那么“不可變”
可以將幾個小元祖組成一個大的元祖,連接操作可用
t = ('third', 'fourth')
t = t + ('fifth', 'sixth') # t = ('third', 'fourth', 'fifth', 'sixth')
重復操作只不過是多次復制同樣的元素,再有,我們前面
提到過可以用一個簡單的函數調用把一個元組變成一個可變的列表。我們的最后一個特性可能
會嚇到你。你可以“修改”特定的元組元素,哇!這意味著什么?
雖然元組對象本身是不可變的,但這并不意味著元組包含的可變對象也不可變了。
>>> t = (['xyz', 123], 23, -103.4)
>>> t
(['xyz', 123], 23, -103.4)
>>> t[0][1]
123
>>> t[0][1] = ['abc', 'def']
>>> t
(['xyz', ['abc', 'def']], 23, -103.4)
雖然 t 是一個元組類型變量, 但是我們設法==通過替換它的第一個元素 (一
個列表對象)的項來“改變”了它。我們替換了 t[0][1],原來是個整數,我們把它替換成了一==
個列表對象 ['abc','def'].雖然我們只是改變了一個可變對象, 但在某種意義上講, 我們也 “改
變”了我們的元組類型變量。
==所有的多對象的,逗號分隔的,沒有明確用符號定義的,比如說像用方括號表示列表和用
圓括號表示元組一樣,等等這些集合默認的類型都是元組,==
==所有函數返回的多對象(不包括有符號封裝的)都是元組類型==
單元素元組
>>> ['abc']
['abc']
>>> type(['abc'
<type 'list'>
>>>
>>> ('xyz')
'xyz'
>>> type(('xyz'
<type 'str'>
圓括號被重載了,它也被用作分組操作符。由圓括號包裹的一個單一元素首
先被作為分組操作,而不是作為元組的分界符。一個變通的方法是在第一個元素后面添一個逗
號(,)來表明這是一個元組而不是在做分組操作
核心筆記:列表 VS 元組(為什么我們要區(qū)分元組和列表變量?)
這個問題也可以被表述為“我們真的需要兩個相似的序列類型嗎?”,一個原因是在有些情況下,使用其中的一種類
型要優(yōu)于使用另一種類型。
最好使用不可變類型變量的一個情況是,如果你在維護一些敏感的數據,并且需要把這些
數據傳遞給一個并不了解的函數(或許是一個根本不是你寫的 API),作為一個只負責一個軟件
某一部分的工程師,如果你確信你的數據不會被調用的函數篡改,你會覺得安全了許多。
一個需要可變類型參數的例子是,如果你在管理動態(tài)數據集合時。你需要先把它們創(chuàng)建出
來,逐漸地或者不定期的添加它們,或者有時還要移除一些單個的元素。這是一個必須使用可
變類型對象的典型例子。幸運的是,通過內建的 list()和 tuple()轉換函數,你可以非常輕松
的在兩者之間進行轉換.
list()和 tuple()函數允許你用一個列表來創(chuàng)建一個元組,反之亦然.如果你有一個元組變
量,但你需要一個列表變量因為你要更新一下它的對象,這時 list()函數就是你最好的幫手.如
果你有一個列表變量,并且想把它傳遞給一個函數,或許一個 API,而你又不想讓任何人弄亂你
的數據,這時 tuple()函數就非常有用
字典的關鍵字
==不可變對象的值是不可改變的。這就意味著它們通過 hash 算法得到的值總是一個值。這是作為字典鍵值的一個必備條件==
拷貝 Python 對象淺拷貝和深拷貝
>>> person = ['name', ['savings', 100.00]]
>>> hubby = person[:] # slice copy
>>> wifey = list(person) # fac func copy
>>> [id(x) for x in person, hubby, wifey]
[11826320, 12223552, 11850936]
>>> hubby[0] = 'joe'
>>> wifey[0] = 'jane'
>>> hubby, wifey
(['joe', ['savings', 100.0]], ['jane', ['savings', 100.0]])
>>> hubby[1][1] = 50.00
>>> hubby, wifey
(['joe', ['savings', 50.0]], ['jane', ['savings', 50.0]])
默認的拷貝都是淺拷貝,對一個對象進行淺拷貝其實是新創(chuàng)建了一個類型跟原對象一樣,其內容是原來對象元素的引用,換句話說,這個拷貝的對象本身是新的,但是它的內容不是。==序列類型對象的淺拷貝是默認類型拷貝,并可以以下幾種方式實施:(1)完全切片操作[:],(2)
利用工廠函數,比如 list(),dict()等,(3)使用 copy 模塊的 copy 函數.==
當妻子的名字被賦值,為什么丈夫的名字沒有受到影響?難道它們
的名字現在不應該都是'jane'了嗎?為什么名字沒有變成一樣的呢?怎么會是這樣呢?這是因為
在這兩個列表的兩個對象中,==第一個對象是不可變的(是個字符串類型),而第二個是可變的(一
個列表).正因為如此,當進行淺拷貝時,字符串被顯式的拷貝,并新創(chuàng)建了一個字符串對象,而列
表元素只是把它的引用復制了一下,并不是它的成員==.所以改變名字沒有任何問題,但是更改他
們銀行賬號的任何信息都會引發(fā)問題.現在,讓我們分別看一下每個列表的元素的對象 ID 值,注
意,銀行賬號對象是同一個對象,這也是為什么對一個對象進行修改會影響到另一個的原因.注
意在我們改變他們的名字后,新的名字字符串是如何替換原有'名字'字符串
==幾點關于拷貝操作的警告。==
第一,非容器類型(比如數字,字符串和其他"原子"類型的
對象,像代碼,類型和 xrange 對象等)沒有被拷貝一說,淺拷貝是用完全切片操作來完成的.第二,
如果元組變量只包含原子類型對象,對它的深拷貝將不會進行.
淺拷貝的時候可變類型的也只拷貝引用,不可變類型新建
序列類型小結
第七章:映象和集合類型
1:映射類型:字典
核心筆記:什么是哈希表?它們與字典的關系是什么?
序列類型用有序的數字鍵做索引將數據以數組的形式存儲。 一般,索引值與所存儲的數據毫無
關系。還可以用另一種方式來存儲數據:基于某種相關值, 比如說一個字符串。
哈希表是一種數據結構:它按照我們所要求的去工作。哈希表中存儲的每一條數據,叫做一個
值(value),是根據與它相關的一個被稱作為鍵(key)的數據項進行存儲的。鍵和值合在一起被稱為
“鍵-值 對”(key-value pairs)。 哈希表的算法是獲取鍵,對鍵執(zhí)行一個叫做哈希函數的操作,
并根據計算的結果,選擇在數據結構的某個地址中來存儲你的值。任何一個值存儲的地址皆取決于
它的鍵。正因為這種隨意性,哈希表中的值是沒有順序的。你擁有的是一個無序的數據集。
你所能獲得的有序集合只能是字典中的鍵的集合或者值的集合。方法 Keys() 或 values() 返回
一個列表,該列表是可排序的。 你還可以用 items()方法得到包含鍵、值對的元組的列表來排序。
由于字典本身是哈希的,所以是無序的。
哈希表一般有很好的性能, 因為用鍵查詢相當快。
如何創(chuàng)建字典和給字典賦值
- dict1 = {}
- fdict = dict((['x', 1], ['y', 2])) 工廠方法
- {}.fromkeys(('x', 'y'), -1) formkeys方法
方法列表
- has_key()
- 'name' in {'name':'tianfav'}
- update()將整個字典的內容添加到另一個字典
- del dict2['name'] # 刪除鍵為“name”的條目
- dict2.clear() # 刪除 dict2 中所有的條目
- del dict2 # 刪除整個 dict2 字典
- dict2.pop('name') # 刪除并返回鍵為“name”的條目
2.映射類型操作符
- 標準類型操作符
- <
- 映射類型操作符
- in ,not in
7.4 映射類型內建方法
- keys(),返回一個列表,包含字典中所有的鍵
- values()方法,返回一個列表,包含字典中所有的值
- items(), 返回一個包含所有(鍵, 值)元組的列表
- update()方法可以用來將一個字典的內容添加到另外一個字典中。字典中原有的鍵如果與新添
加的鍵重復,那么重復鍵所對應的原有條目的值將被新鍵所對應的值所覆蓋。原來不存在的條目則
被添加到字典中 - clear()方法可以用來刪除字典中的所有的條目
>>> dict2= {'host':'earth', 'port':80}
>>> dict3= {'host':'venus', 'server':'http'}
>>> dict2.update(dict3)
>>> dict2
{'server': 'http', 'port': 80, 'host': 'venus'}
>>> dict3.clear()
>>> dict3
- setdefault()是自 2.0 才有的內建方法, 使得代碼更加簡潔,它實現了常用的語法: 檢查字典
中是否含有某鍵。 如果字典中這個鍵存在,你可以取到它的值。 如果所找的鍵在字典中不存在,
你可以給這個鍵賦默認值并返回此值
>>> myDict = {'host': 'earth', 'port': 80}
>>> myDict.keys()
['host', 'port']
>>> myDict.items()
[('host', 'earth'), ('port', 80)]
>>> myDict.setdefault('port', 8080)
80
>>> myDict.setdefault('prot', 'tcp')
'tcp'
>>> myDict.items()
[('prot', 'tcp'), ('host', 'earth'), ('port', 80)]
7.5 字典的鍵
7.5.1 不允許一個鍵對應多個值
7.5.2 鍵必須是可哈希的
為什么鍵必須是可哈希的?
解釋器調用哈希函數,根據字典中鍵的值來計算存儲你的數據的位
置。如果鍵是可變對象,它的值可改變。如果鍵發(fā)生變化,哈希函數會映射到不同的地址來存儲數
據。如果這樣的情況發(fā)生,哈希函數就不可能可靠地存儲或獲取相關的數據。選擇可哈希的鍵的原
因就是因為它們的值不能改變
7.6 集合類型
集合對象是一組無序排列的可哈希的值。
是的,集合成員可以做字典中的鍵。數學集合轉為 Python 的集合對象很有效,集合關系測試和 union、
intersection 等操作符在 Python 里也同樣如我們所預想地那樣工作
集合(sets)有兩種不同的類型,可變集合(set) 和 不可變集合(frozenset)。如你所想,對可
變集合(set),你可以添加和刪除元素,對 不可變集合(frozenset)則不允許這樣做。請注意,可變
集合(set)不是可哈希的,因此既不能用做字典的鍵也不能做其他集合中的元素。不可變集合
(frozenset)則正好相反,即,他們有哈希值,能被用做字典的鍵或是作為集合中的一個成員
八:循環(huán)和條件
8.11 迭代器和 iter() 函數
迭代器是在版本 2.2 被加入 Python 的, 它為類序列對象提供了一個類序列的接口. 我們在
前邊的第 6 章已經正式地介紹過序列. 它們是一組數據結構,你可以利用它們的索引從 0 開始一直
"迭代" 到序列的最后一個條目
迭代器就是有一個 next() 方法的對象, 而不是通過索引來計數. 當你或是一個循
環(huán)機制(例如 for 語句)需要下一個項時, 調用迭代器的 next() 方法就可以獲得它. 條目全部取
出后, 會引發(fā)一個 StopIteration 異常, 這并不表示錯誤發(fā)生, 只是告訴外部調用者, 迭代完成
為什么要迭代器?
援引 PEP (234) 中對迭代器的定義:
- 提供了可擴展的迭代器接口.
- 對列表迭代帶來了性能上的增強.
- 在字典迭代中性能提升.
- 創(chuàng)建真正的迭代接口, 而不是原來的隨機對象訪問.
- 與所有已經存在的用戶定義的類以及擴展的模擬序列和映射的對象向后兼容
- 迭代非序列集合(例如映射和文件)時, 可以創(chuàng)建更簡潔可讀的代碼.
如何迭代
根本上說, 迭代器就是有一個 next() 方法的對象, 而不是通過索引來計數. 當你或是一個循
環(huán)機制(例如 for 語句)需要下一個項時, 調用迭代器的 next() 方法就可以獲得它. 條目全部取
出后, 會引發(fā)一個 StopIteration 異常, 這并不表示錯誤發(fā)生, 只是告訴外部調用者, 迭代完成.
不過, 迭代器也有一些限制. 例如你不能向后移動, 不能回到開始, 也不能復制一個迭代器.
如果你要再次(或者是同時)迭代同個對象, 你只能去創(chuàng)建另一個迭代器對象. 不過, 這并不糟糕,因為還有其他的工具來幫助你使用迭代器.
reversed() 內建函數將返回一個反序訪問的迭代器. enumerate() 內建函數同樣也返回迭代器.
另外兩個新的內建函數, any() 和 all() , 在 Python 2.5 中新增, 如果迭代器中某個/所有條目
的值都為布爾真時,則它們返回值為真. 本章先前部分我們展示了如何在 for 循環(huán)中通過索引或是
可迭代對象來遍歷條目. 同時 Python 還提供了一整個 itertools 模塊, 它包含各種有用的迭代
器.
使用迭代器
===序列===
>>> myTuple = (123, 'xyz', 45.67)
>>> i = iter(myTuple)
>>> i.next()
123
for 循環(huán)會自動調用迭代器的 next() 方法(以及監(jiān)視StopIteration 異常).
==字典==
字典和文件是另外兩個可迭代的 Python 數據類型. 字典的迭代器會遍歷它的鍵(keys).
語句 for eachKey in myDict.keys() 可以縮寫為 for eachKey in myDict
==文件==
文件對象生成的迭代器會自動調用 readline() 方法. 這樣, 循環(huán)就可以訪問文本文件的所有行 . 程 序 員 可 以 使 用 更 簡 單 的 for eachLine in myFile 替 換 for eachLine in myFile.readlines() :
>>> myFile = open('config-win.txt')
>>> for eachLine in myFile:
... print eachLine, # comma suppresses extra \n
...
[EditorWindow]
font-name: courier new
font-size: 10
>>> myFile.close()
列表解析
>>> [(x+1,y+1) for x in range(3) for y in range(5)]
[(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 1), (2, 2), (2,
3), (2, 4), (2, 5), (3, 1), (3, 2), (3, 3), (3, 4), (3, 5)]
8.13 生成器表達式
列表解析的一個不足就是必要生成所有的數據, 用以創(chuàng)建整個列表. 這可能對有大量數據的迭
代器有負面效應. 生成器表達式通過結合列表解析和生成器解決了這個問題
生成器表達式在 Python 2.4 被引入, 它與列表解析非常相似,而且它們的基本語法基本相同;
不過它并不真正創(chuàng)建數字列表, 而是返回一個生成器,這個生成器在每次計算出一個條目后,把這
個條目“產生” (yield)出來. 生成器表達式使用了"延遲計算"(lazy evaluation), 所以它在使用內存上更有效
- 列表解析:
- [expr for iter_var in iterable if cond_expr]
- 生成器表達式:
- (expr for iter_var in iterable if cond_expr)
x_product_pairs = ((i, j) for i in rows for j in cols())
現在我們可以循環(huán) x_product_pairs , 它會懶惰地循環(huán) rows 和 cols :
>>> for pair in x_product_pairs:
... print pair
...
(1, 56)
(1, 2)
(1, 1)
(2, 56)
(2, 2)
(2, 1)
(3, 56)
(3, 2)
(3, 1)
(17, 56)
(17, 2)
(17, 1)
如何一步步使用生成器表達式(重要,文件打開的時候不要用readlines,會一次將所有的內容都打開)
=== 重構樣例 ===
我們通過一個尋找文件最長的行的例子來看看如何改進代碼. 在以前, 我們這樣讀取文件:
f = open('/etc/motd', 'r')
longest = 0
while True:
if not linelen:
break
if linelen > longest:
longest = linelen
f.close()
return longest
事實上, 這還不夠老. 真正的舊版本 Python 代碼中, 布爾常量應該寫是整數 1 , 而且我們應
該使用 string 模塊而不是字符串的 strip() 方法:
import string
len(string.strip(f.readline()))
從那時起, 我們認識到如果讀取了所有的行, 那么應該盡早釋放文件資源. 如果這是一個很多
進程都要用到的日志文件, 那么理所當然我們不能一直拿著它的句柄不釋放. 是的, 我們的例子是
用來展示的, 但是你應該得到這個理念. 所以讀取文件的行的首選方法應該是這樣:
f = open('/etc/motd', 'r')
longest = 0
allLines = f.readlines()
f.close()
for line in allLines:
linelen = len(line.strip())
if linelen > longest:
longest = linelen
return longest
列表解析允許我們稍微簡化我們代碼, 而且我們可以在得到行的集合前做一定的處理. 在下段
代碼中, 除了讀取文件中的行之外,我們還調用了字符串的 strip() 方法處理行內容.
f = open('/etc/motd', 'r')
longest = 0
allLines = [x.strip() for x in f.readlines()]
f.close()
for line in allLines:
linelen = len(line)
if linelen > longest:
longest = linelen
return longest
然而, 兩個例子在處理大文件時候都有問題, 因為 readlines() 會讀取文件的所有行. 后來
我們有了迭代器, 文件本身就成為了它自己的迭代器, 不需要調用 readlines() 函數. 我們已經
做到了這一步, 為什么不去直接獲得行長度的集合呢(之前我們得到的是行的集合)? 這樣, 我們就
可以使用 max() 內建函數得到最長的字符串長度:
f = open('/etc/motd', 'r')
allLineLens = [len(x.strip()) for x in f]
f.close()
return max(allLineLens)
這里唯一的問題就是你一行一行迭代 f 的時候, 列表解析需要文件的所有行讀取到內存中,
然后生成列表. 我們可以進一步簡化代碼: 使用生成器表達式替換列表解析, 然后把它移到 max()
函數里, 這樣, 所有的核心部分只有一行:
f = open('/etc/motd', 'r')
longest = max(len(x.strip()) for x in f)
f.close()
return longest
最后, 我們可以去掉文件打開模式(默認為讀取), 然后讓 Python 去處理打開的文件. 當然,
文件用于寫入的時候不能這么做, 但這里我們不需要考慮太多:
return max(len(x.strip()) for x in open('/etc/motd')
九:文件和輸入輸出
9.1文件對象
文件對象不僅可以用來訪問普通的磁盤文件, 而且也可以訪問任何其它類型抽象層面上的"文
件". 一旦設置了合適的"鉤子", 你就可以訪問具有文件類型接口的其它對象, 就好像訪問的是普通文件一樣
內建函數 open() 返回一個文件對象(參見下一小節(jié)), 對該文件進行后繼相關的操作都要用到
它. 還有大量的函數也會返回文件對象或是類文件( file-like )對象. 進行這種抽象處理的主要原
因是許多的輸入/輸出數據結構更趨向于使用通用的接口. 這樣就可以在程序行為和實現上保持一
致性. 甚至像 Unix 這樣的操作系統(tǒng)把文件作為通信的底層架構接口. 請記住, 文件只是連續(xù)的字
節(jié)序列. 數據的傳輸經常會用到字節(jié)流, 無論字節(jié)流是由單個字節(jié)還是大塊數據組成.
open() 的基本語法是:
file_object = open(file_name, access_mode='r', buffering=-1)
file_name 是包含要打開的文件名字的字符串, 它可以是相對路徑或者絕對路徑. 可選變量
access_mode 也是一個字符串, 代表文件打開的模式. 通常, 文件使用模式 'r', 'w', 或是 'a'
模式來打開, 分別代表讀取, 寫入和追加. 還有個 'U' 模式, 代表通用換行符支持(見下).
使用 'r' 或 'U' 模式打開的文件必須是已經存在的. 使用 'w' 模式打開的文件若存在則首
先清空, 然后(重新)創(chuàng)建. 以 'a' 模式打開的文件是為追加數據作準備的, 所有寫入的數據都將
追加到文件的末尾. 即使你 seek 到了其它的地方. 如果文件不存在, 將被自動創(chuàng)建, 類似以 'w'
模式打開文件.
open() 和 file() 函數具有相同的功能, 可以任意替換
通用換行符支持(UNS)
在下一個核心筆記中, 我們將介紹如何使用 os 模塊的一些屬性來幫助你在不同平臺下訪問文
件, 不同平臺用來表示行結束的符號是不同的, 例如 \n, \r, 或者 \r\n . 所以, Python 的解釋器也要處理這樣的任務, 特別是在導入模塊時分外重要。 你難道不希望 Python 用相同的方式處理
所有文件嗎?
這就是 UNS 的關鍵所在, 作為 PEP 278 的結果, Python 2.3 引入了 UNS. 當你使用 'U' 標志
打開文件的時候, 所有的行分割符(或行結束符, 無論它原來是什么)通過 Python 的輸入方法(例
如 read*() )返回時都會被替換為換行符 NEWLINE(\n). ('rU' 模式也支持 'rb' 選項) . 這個特性
還支持包含不同類型行結束符的文件. 文件對象的 newlines 屬性會記錄它曾“看到的”文件的行結
束符.
如果文件剛被打開, 程序還沒有遇到行結束符, 那么文件的 newlines 為 None .在第一行被讀
取后, 它被設置為第一行的結束符. 如果遇到其它類型的行結束符, 文件的 newlines 會成為一個
包含每種格式的元組. 注意 UNS 只用于讀取文本文件. 沒有對應的處理文件輸出的方法.
在編譯 Python 的時候,UNS 默認是打開的. 如果你不需要這個特性, 在運行 configure 腳本
時,你可以使用 --without-universal-newlines 開關關閉它. 如果你非要自己處理行結束符, 請
查閱核心筆記,使用 os 模塊的相關屬性.
9.3文件內建方法
==輸入==
- read() 方法用來直接讀取字節(jié)到字符串中, 最多讀取給定數目個字節(jié). 如果沒有給定 size
參數(默認值為 -1)或者 size 值為負, 文件將被讀取直至末尾. 未來的某個版本可能會刪除此方
法. - readline() 方法讀取打開文件的一行(讀取下個行結束符之前的所有字節(jié)). 然后整行,包括行
結束符,作為字符串返回. 和 read() 相同, 它也有一個可選的 size 參數, 默認為 -1, 代表讀至
行結束符. - readlines() 方法并不像其它兩個輸入方法一樣返回一個字符串. 它會讀取所有(剩余的)行然
后把它們作為一個字符串列表返回. 它的可選參數 sizhint 代表返回的最大字節(jié)大小. 如果它大
于 0 , 那么返回的所有行應該大約有 sizhint 字節(jié)(可能稍微大于這個數字, 因為需要湊齊緩沖區(qū)
大小).
==輸出==
- write() 內建方法功能與 read() 和 readline() 相反. 它把含有文本數據或二進制數據塊的
字符串寫入到文件中去. - writelines() 方法是針對列表的操作, 它接受一個字符串列表作為參
數 , 將 它 們 寫 入 文 件 . 行 結 束 符 并 不 會 被 自 動 加 入 , 所 以 如 果 需 要 的 話 , 你 必 須 在 調 用writelines()前給每行結尾加上行結束符.
==文件內移動==
- seek() 方法(類似 C 中的 fseek() 函數)可以在文件中移動文件指針到不同的位置
- text() 方法是對 seek() 的補充; 它告訴你當前文件指針在文件中的位置 - 從文件起始算起,單位為字節(jié)
==文件迭代==
read()方法和for循環(huán)一樣可以迭代文件
在 Python 2.2 之前, 從文件中讀取行的最好辦法是使用 file.readlines() 來讀取所有數據,
這樣程序員可以盡快釋放文件資源. 如果不需要這樣做, 那么程序員可以調用 file.readline()
一次讀取一行. 曾有一段很短的時間, file.xreadlines() 是讀取文件最高效的方法.
在 Python 2.2 中, 我們引進了迭代器和文件迭代, 這使得一切變得完全不同, 文件對象成為
了它們自己的迭代器, 這意味著用戶不必調用 read*() 方法就可以在 for 循環(huán)中迭代文件的每一行.
另外我們也可以使用迭代器的 next 方法, file.next() 可以用來讀取文件的下一行. 和其它迭代
器一樣, Python 也會在所有行迭代完成后引發(fā) StopIteration 異常.
所以請記得, 如果你見到這樣的代碼, 這是"完成事情的老方法", 你可以安全地刪除對
readline() 的調用.
for eachLine in f.readline():
:
文件迭代更為高效, 而且寫(和讀)這樣的 Python 代碼更容易. 如果你是 Python 新人, 那
么請使用這些新特性, 不必擔心它們過去是如何.
close() 通過關閉文件來結束對它的訪問. Python 垃圾收集機制也會在文件對象的引用計數降至零的時候自動關閉文件.
行分隔符
操作系統(tǒng)間的差異之一是它們所支持的行分隔符不同.
Python 的 os 模塊設計者已經幫我們想到了這些問題. os 模塊有五個很
有用的屬性如下
- os 模塊屬性 描述
- linesep 用于在文件中分隔行的字符串
- sep 用來分隔文件路徑名的字符串
- pathsep 用于分隔文件路徑的字符串
- curdir 當前工作目錄的字符串名稱
- pardir (當前工作目錄的)父目錄字符串名稱
不管你使用的是什么平臺, 只要你導入了 os 模塊, 這些變量自動會被設置為正確的值, 減少
了你的麻煩
9.4 文件內建屬性
文件對象除了方法之外,還有一些數據屬性. 這些屬性保存了文件對象相關的附加數據, 例如
文件名(file.name ), 文件的打開模式 ( file.mode ), 文件是否已被關閉 ( file.closed), 以及
一 個 標 志 變 量 , 它 可 以 決 定 使 用 print 語 句 打 印 下 一 行 前 是 否 要 加 入 一 個 空 白 字 符
( file.softspace ).
9.5 標準文件
- sys.stdout
- sys.stdin
- sys.stderr
9.6 命令行參數
sys 模塊通過 sys.argv 屬性提供了對命令行參數的訪問
9.7文件系統(tǒng)
對文件系統(tǒng)的訪問大多通過 Python 的 os 模塊實現. 該模塊是 Python 訪問操作系統(tǒng)功能的主
要接口. os 模塊實際上只是真正加載的模塊的前端, 而真正的那個"模塊"明顯要依賴與具體的操作
系統(tǒng).
#!/usr/bin/env python
2
3 import os
Edit By Vheavens
Edit By Vheavens
4 for tmpdir in ('/tmp', r'c:\temp'):
5 if os.path.isdir(tmpdir):
6 break
7 else:
8 print 'no temp directory available'
9 tmpdir = ''
10
11 if tmpdir:
12 os.chdir(tmpdir)
13 cwd = os.getcwd()
14 print '*** current temporary directory'
15 print cwd
16
17 print '*** creating example directory...'
18 os.mkdir('example')
19 os.chdir('example')
20 cwd = os.getcwd()
21 print '*** new working directory:'
22 print cwd
23 print '*** original directory listing:'
24 print os.listdir(cwd)
25
26 print '*** creating test file...'
27 fobj = open('test', 'w')
28 fobj.write('foo\n')
29 fobj.write('bar\n')
30 fobj.close()
31 print '*** updated directory listing:'
32 print os.listdir(cwd)
33
34 print "*** renaming 'test' to 'filetest.txt'"
35 os.rename('test', 'filetest.txt')
36 print '*** updated directory listing:'
37 print os.listdir(cwd)
38
39 path = os.path.join(cwd, os.listdir (cwd)[0])
40 print '*** full file pathname'
41 print path
42 print '*** (pathname, basename) =='
43 print os.path.split(path)
44 print '*** (filename, extension) =='
45 print os.path.splitext(os.path.basename(path))
46
47 print '*** displaying file contents:'
48 fobj = open(path)
49 for eachLine in fobj:
50 print eachLine,
51 fobj.close()
52
53 print '*** deleting test file'
54 os.remove(path)
55 print '*** updated directory listing:'
56 print os.listdir(cwd)
57 os.chdir(os.pardir)
58 print '*** deleting test directory'
59 os.rmdir('example')
60 print '*** DONE'
9.9永久存儲
==shelve 模塊==
十:錯誤和異常
10.1 什么是異常
對異常的最好描述是: 它是因為程序出現了錯誤而在正常控制流以外采取的行為. 這個行為又
分為兩個階段: 首先是引起異常發(fā)生的錯誤,然后是檢測(和采取可能的措施)階段
10.2 Python 中的異常
10.3 檢測和處理異常
核心筆記: 忽略代碼, 繼續(xù)執(zhí)行, 和向上移交
try 語句塊中異常發(fā)生點后的剩余語句永遠不會到達(所以也永遠不會執(zhí)行). 一旦一個異常被
引發(fā), 就必須決定控制流下一步到達的位置. 剩余代碼將被忽略, 解釋器將搜索處理器, 一旦找到,
就開始執(zhí)行處理器中的代碼.
如果沒有找到合適的處理器, 那么異常就向上移交給調用者去處理, 這意味著堆棧框架立即回
到之前的那個.
如果在上層調用者也沒找到對應處理器, 該異常會繼續(xù)被向上移交, 直到找到合適處理器. 如果到達最頂層仍然沒有找到對應處理器, 那么就認為這個異常是未處理的, Python 解釋器會顯示出跟蹤返回消息, 然后退出.
核心筆記: 忽略代碼, 繼續(xù)執(zhí)行, 和向上移交
try 語句塊中異常發(fā)生點后的剩余語句永遠不會到達(所以也永遠不會執(zhí)行). 一旦一個異常被引發(fā), 就必須決定控制流下一步到達的位置. 剩余代碼將被忽略, 解釋器將搜索處理器, 一旦找到,
就開始執(zhí)行處理器中的代碼.如果沒有找到合適的處理器, 那么異常就向上移交給調用者去處理, 這意味著堆棧框架立即回
到之前的那個.
如果在上層調用者也沒找到對應處理器, 該異常會繼續(xù)被向上移交, 直到找到合適
處理器. 如果到達最頂層仍然沒有找到對應處理器, 那么就認為這個異常是未處理的, Python 解釋
器會顯示出跟蹤返回消息, 然后退出.
10.3.6 異常參數
異常也可以有參數, 異常引發(fā)后它會被傳遞給異常處理器. 當異常被引發(fā)后參數是作為附加幫
助信息傳遞給異常處理器的. 雖然異常原因是可選的, 但標準內建異常提供至少一個參數, 指示異
常原因的一個字符串.
異常的參數可以在處理器里忽略, 但 Python 提供了保存這個值的語法. 我們已經在上邊接觸
到相關內容: 要想訪問提供的異常原因, 你必須保留一個變量來保存這個參數. 把這個參數放在
except 語句后, 接在要處理的異常后面
else 子句
我們已經看過 else 語句段配合其他的 Python 語句,比如條件和循環(huán).至于 try-except 語句段,
它的功能和你所見過的其他 else 沒有太多的不同:在 try 范圍中沒有異常被檢測到時,執(zhí)行 else 子句.
在 else 范圍中的任何代碼運行前,try 范圍中的所有代碼必須完全成功(也就是,結束前沒有引發(fā)
異常)
try-finally 語句
try-finally 語句和 try-except
區(qū)別在于它不是用來捕捉異常的.作為替代,它常常用來維持一致的行為而無論異常是否發(fā)生.我們得知無論 try 中是否有異常觸發(fā),finally 代碼段都會被執(zhí)行
10.4 上下文管理
10.4.1 with 語句
類似 try-except-finally , with 語句也是用來簡化代碼的,這與用 try-except 和 try-finally
所想達到的目的前后呼應.try-except 和 try-finally 的一種特定的配合用法是保證共享的資源的
唯一分配,并在任務結束的時候釋放它.比如==文件(數據,日志,數據庫等等),線程資源,簡單同步,數據庫連接==,等等. with 語句的目標就是應用在這種場景.
with 語句的目的在于從流程圖中把 try,except 和 finally 關鍵字和資源分配釋放相關
代碼統(tǒng)統(tǒng)去掉, 而不是像 try-except-finally 那樣僅僅簡化代碼使之易用. with 語法的基本用法
看上去如下:
with context_expr [as var]:
with_suite
*上下文管理協議
下面描述了一些關于協議如何和文件對象協同工作.讓我們在此進一步地研究
上下文表達式(context_expr),上下文管理器
當 with 語句執(zhí)行時,便執(zhí)行上下文符號(譯者注:就是 with 與 as 間內容)來獲得一個上下文管理
器.上下文管理器的職責是提供一個上下文對象.這是通過調用context()方法來實現的.該方法
返回一個上下文對象,用于在 with 語句塊中處理細節(jié).有點需要注意的是上下文對象本身就可以是上
下文管理器.所以 context_expr 既可以為一個真正的上下文管理器,也可以是一個可以自我管理的上
下文對象.在后一種情況時,上下文對象仍然有context()方法,返回其自身,如你所想
上下文對象,with 語句塊
一旦我們獲得了上下文對象,就會調用它的enter()方法.它將完成 with 語句塊執(zhí)行前的所有準備工作.你可以注意到在上面的 with 行的語法中有一個可選的 as 聲明變量跟隨在 context_expr
之后.如果提供提供了變量,以enter()返回的內容來賦值;否則,丟棄返回值.
在我們的文件對象例子中,上下文對象的enter()返回文件對象并賦值給 f.現在,執(zhí)行了 with 語句塊.當 with 語句塊執(zhí)行結束,無論是"和諧地"還是由于異常,都會調用上
下文對象的exit()方法.exit()有三個參數.如果 with 語句塊正常結束,三個參數全部是
None.如果發(fā)生異常,三個參數的值的分別等于調用 sys.exc_info()函數(見 10.12)返回的三個值:類
型(異常類),值(異常實例),和回溯(traceback),相應的回溯對象.
你可以自己決定如何在exit()里面處理異常.慣例是當你處理完異常時不返回任何值,或
返回 None,或返回其他布爾值為 False 對象.這樣可以使異常拋給你的用戶來處理.如果你明確的想
屏蔽這個異常,返回一個布爾為 True 的值.如果沒有發(fā)生異常或你在處理異常后返回 True,程序會繼
續(xù)執(zhí)行 with 子句后的下一段代碼.
因為==上下文管理器主要作用于共享資源==,你可以==想象到enter()和exit()方法基本是干
的需要分配和釋放資源的低層次工作,比如:
數據庫連接,鎖分配,信號量加減,狀態(tài)管理,打開/關閉文件,異常處理,等等.
為 了 幫 助 你 編 寫 對 象 的 上 下 文 管 理 器 , 有 一 個 contextlib 模 塊 , 包 含 了 實 用 的
functions/decorators, 你 可 以 用 在 你 的 函 數 / 對 象 上 而 不 用 去 操 心 關 于 類 或
context(),enter(),enter(),exit()這些方法的實現==.
10.5 *字符串作為異常
10.6 觸發(fā)異常
10.6.1 raise 語句
語法與慣用法
raise 語句對所支持是參數十分靈活,對應到語法上就是支持許多不同的格式.rasie 一般的用法是:
raise [SomeException [, args [, traceback]]]
最常見的用法為 SomeException 是一個類.不需要其他的參數,但如果有的話,可以是一個單一對
象參數,一個參數的元組,或一個異常類的實例.如果參數是一個實例,可以由給出的類及其派生類實
例化(已存在異常類的子集).若參數為實例,則不能有更多的其他參數.
當參數是一個實例的時候會發(fā)生什么呢? 該實例若是給定異常類的實例當然不會有問題, 然而,
如果該實例并非這個異常類或其子類的實例時, 那么解釋器將使用該實例的異常參數創(chuàng)建一個給定
異常類的新實例. 如果該實例是給定異常類子類的實例, 那么新實例將作為異常類的子類出現, 而
不是原來的給定異常類
10.8 標準異常
下面所有的 Python 當前的標準異常集,所有的異常都是內建的. 所以它們在腳本啟動前或在互交命令行提示符出現時已經是可用的了:
異常名稱 描述
BaseExceptiona 所有異常的基類
SystemExitb python 解釋器請求退出
KeyboardInterruptc 用戶中斷執(zhí)行(通常是輸入^C)
Exceptiond 常規(guī)錯誤的基類
StopIteratione 迭代器沒有更多的值
GeneratorExita 生成器(generator)發(fā)生異常來通知退出
SystemExith Python 解釋器請求退出
StandardErrorg 所有的內建標準異常的基類
ArithmeticErrord 所有數值計算錯誤的基類
FloatingPointErrord 浮點計算錯誤
OverflowError 數值運算超出最大限制
ZeroDivisionError 除(或取模)零 (所有數據類型)
AssertionErrord 斷言語句失敗
AttributeError 對象沒有這個屬性
EOFError 沒有內建輸入,到達 EOF 標記
EnvironmentErrord 操作系統(tǒng)錯誤的基類
IOError 輸入/輸出操作失敗
OSErrord 操作系統(tǒng)錯誤
WindowsErrorh Windows 系統(tǒng)調用失敗
ImportError 導入模塊/對象失敗
KeyboardInterruptf 用戶中斷執(zhí)行(通常是輸入^C)
LookupErrord 無效數據查詢的基類
IndexError 序列中沒有沒有此索引(index)
KeyError 映射中沒有這個鍵
MemoryError 內存溢出錯誤(對于 Python 解釋器不是致命的)
NameError 未聲明/初始化對象 (沒有屬性)
UnboundLocalErrorh 訪問未初始化的本地變量
ReferenceErrore 弱引用(Weak reference)試圖訪問已經垃圾回收了的對象
RuntimeError 一般的運行時錯誤
NotImplementedErrord 尚未實現的方法
SyntaxError Python 語法錯誤
IndentationErrorg 縮進錯誤
TabErrorg Tab 和空格混用
SystemError 一般的解釋器系統(tǒng)錯誤
TypeError 對類型無效的操作
ValueError 傳入無效的參數
UnicodeErrorh Unicode 相關的錯誤
UnicodeDecodeErrori Unicode 解碼時的錯誤
UnicodeEncodeErrori Unicode 編碼時錯誤
UnicodeTranslateErrorf Unicode 轉換時錯誤
Warningj 警告的基類
DeprecationWarningj 關于被棄用的特征的警告
FutureWarningi 關于構造將來語義會有改變的警告
OverflowWarningk 舊的關于自動提升為長整型(long)的警告
PendingDeprecationWarningi 關于特性將會被廢棄的警告
RuntimeWarningj 可疑的運行時行為(runtime behavior)的警告
SyntaxWarningj 可疑的語法的警告
UserWarningj 用戶代碼生成的警告
10.9 *創(chuàng)建異常
10.10 為什么用異常(現在)?
毫無疑問,錯誤的存在會伴隨著軟件的存在.區(qū)別在于當今快節(jié)奏的計算世界, 我們的執(zhí)行環(huán)境
已經改變, 所以我們需要改變錯誤處理, 以準確反映我們軟件的開發(fā)環(huán)境. 就現今應用來說, 普遍
的是自洽(self-contained)的圖形用戶界面(GUIs)或是客戶機/服務器體系, 例如 Web.
在應用層處理錯誤的能力近來變得更為重要, 用戶已不再是應用程序的的唯一的直接運行者.
隨著互聯網和網上電子商業(yè)應用越來越普及, web 服務器將成為應用軟件的主要客戶. 這意味著應用
程序再也不能只是直接的失敗或崩潰, 因為如果這樣, 系統(tǒng)錯誤導致瀏覽器的錯誤, 這反過來又
會讓用戶沮喪. 失去眼球意味著失去廣告收入和和潛在的大量無可挽回的生意.
如果錯誤的確發(fā)生了, 它們一般都歸因于用戶輸入的數據無效. 運行環(huán)境必須足夠強健,來處
理應用級別的錯誤,并提供用戶級別的錯誤信息.就服務器而言,這必須轉化為一個"非錯誤" . 因為
應用必須要成功的完成, 即使所做的不過是返回一個錯誤的信息, 向用戶是提供一個有效的超文本標記語言(HTML)的網頁指明錯誤.
如果你不清楚我在說什么, 那個一個簡單的網頁瀏覽器窗口,用大而黑的字體寫到"內部服務器
錯誤"是否更耳熟?用一個彈出式窗口宣告"文件中沒有數據"的致命錯誤如何?作為一個用戶, 這
些詞語對你有意義嗎?沒有, 當然沒有(除非你是一個互聯網軟件工程師), 至于對普通用戶來說,
這些是無休止的混亂和挫折感的來源. 這些錯誤導致在執(zhí)行的程序時的失敗. 應用不論是返回無效
的超文本傳輸協議 ( http)數據還是致命地終止, 都會導致 Web 服務器舉手投降, 說: "我放棄" !
這種類型的執(zhí)行錯誤不應該被允許, 無論情況如何. 隨著系統(tǒng)變得更加復雜, 又牽涉到更多的新手用戶, 要采取額外的措施, 確保用戶平滑的學到應用經驗. 即使面對一個錯誤, 應用應該成功
的中止, 不至于災難性的影響其執(zhí)行環(huán)境. Python 異常處理促使成熟和正確的編程.
10.11 到底為什么要異常?
因為錯誤的數據需要在調用層次中向上轉發(fā),
但在前進的道路上可能被曲解. 一個不相干的錯誤可能會被宣布為起因,而實際上它與原始問題完
全無關.在一層一層的傳遞中,我們失去了對原始錯誤封裝和保管的能力,更不用說完全地失去我們
原本關心的數據的蹤影!異常不僅簡化代碼, 而且簡化整個錯誤管理體系 --- 它不該在應用開發(fā)中
如此重要角色;而有了 Python 的異常處理能力, 也的確沒有必要了
10.12 異常和 sys 模塊
另一種獲取異常信息的途徑是通過 sys 模塊中 exc_info()函數. 此功能提供了一個 3 元組(3-tuple)的信息, 多于我們單純用異常參數所能獲得.