它們的成員有序排列的,而且能夠經過下標偏移量訪問到它的一個或者幾個成員,稱爲序列,包括下面這些:字符串(普通字符串和unicode 字符串),列表,和元組類型。html
首先咱們來熟悉一下適用於全部序列類型的操做符和內建函數(BIFs),python
簡介git
操做符程序員
內建函數web
內建函數(若是可用)正則表達式
特性(若是可用)算法
相關模塊(若是可用)shell
序列類型有着相同的訪問模式:它的每個元素能夠經過指定一個偏移量的方式獲得。而多個元素能夠經過切片操做的方式一次獲得。數據庫
下標偏移量是從0 開始到 總元素數-1 結束 -- 之因此要減一是由於咱們是從0 開始計數的。編程
標準類型操做符(參見4.5 節)通常都能適用於全部的序列類型。固然,若是做複合類型的對象比較的話,這樣說可能須要有所保留,不過其餘的操做絕對是徹底適用的。
表6.1 列出了對全部序列類型都適用的操做符。操做符是按照優先級從高到底的順序排列的。
鏈接操做符( + )
這個操做符容許咱們把一個序列和另外一個相同類型的序列作鏈接。語法以下:
sequence1 + sequence2
注意,這個操做不是最快或者說最有效的。對字符串來講,這個操做不如把全部的子字符串放到一個列表或可迭代對象中,而後調用一個join方法來把全部的內容鏈接在一塊兒節約內存;相似地,對列表來講,咱們推薦讀者用列表類型的extend()方法來把兩個或者多個列表對象合併.當你須要簡單地把兩個對象的內容合併,或者說不能依賴於可變對象的那些沒有返回值(實際上它返回一個None)的內建方法來完成的時候時,鏈接操做符仍是很方便的一個選擇。
重複操做符 ( * )
當你須要須要一個序列的多份拷貝時,重複操做符很是有用,它的語法以下:
sequence * copies_int
copies_int 必須是一個整數(1.6 節裏面有講到,不能是長整數).像鏈接操做符同樣,該操做符返回一個新的包含多份原對象拷貝的對象。
切片操做符 ( [], [:], [::] )
序列容許經過指定下標的方式來得到某一個數據元素,或者經過指定下標範圍來得到一組序列的元素.這種訪問序列的方式叫作切片。訪問某一個數據元素的語法以下:
sequence[index]
偏移量能夠是正值,範圍從0 到偏移最大值(比序列長度少一),用len()函數能夠獲得序列長度,實際範圍: 0 <= inde <= len(sequece)-1 ;
使用負索引,範圍是 -1 到序列的負長度,-len(sequence), -len(sequence) <= index <= -1;
正負索引的區別在於正索引以序列的開始爲起點,負索引以序列的結束爲起點.試圖訪問一個越界的索引會引起一個以下的異常:
>>> names = ('Faye', 'Leanna', 'Daylen') >>> print names[4] Traceback (most recent call last): File "<stdin>", line 1, in ? IndexError: tuple index out of range
由於Python 是面向對象的,因此你能夠像下面這樣直接訪問一個序列的元素(不用先把它賦值給一個變量):
>>> print ('Faye', 'Leanna', 'Daylen')[1] Leanna
這個特性在你調用一個返回值是序列類型的函數,而且你只對返回的序列中的一個或某幾個元素感興趣時特別有用.
如何一次獲得多個元素呢?
sequence[starting_index:ending_index]
起始索引和結束索引都是可選的,若是沒有提供或者用None 做爲索引值,切片操做會從序列的最開始處開始,或者直到序列的最末尾結束.
用步長索引來進行擴展的切片操做
多出來的第三個索引值被用作步長參數。 Python 的虛擬機裏面其實很早就有了擴展切片操做,eg:
>>> s = 'abcdefgh' >>> s[::-1] # 能夠視做"翻轉"操做 'hgfedcba' >>> s[::2] # 隔一個取一個的操做 'aceg'
切片索引的更多內容
切片索引的語法要比簡單的單一元素索引靈活的多。開始和結束素引值能夠超過字符串的長度。簡單地說,即便用100 來做爲一個長度不到100 的序列的結束索引也不會有什麼錯誤,例子以下:
>>> ('Faye', 'Leanna', 'Daylen')[-100:100] ('Faye', 'Leanna', 'Daylen')
有一個字符串,咱們想經過一個循環按照這樣的形式顯示它:每次都把位於最後的一個字符砍掉,下面是實現這個要求的一種方法:
>>> s = 'abcde' >>> i = -1 >>> for i in range(-1, -len(s), -1): ... print s[:i] ... abcd abc ab a
但是,該如何在第一次迭代的時候顯示整個字符串呢?是否有一種方法能夠不用在整個循環以前加入一個額外的print 語句呢?以負數做爲索引的例子裏是不能解決這個的方法,由於-1 已是「最小」的索引了。
可使用另外一個小技巧:用None 做爲索引值,這樣一來就能夠知足你的須要,好比說,在你想用一個變量做爲索引來從第一個到遍歷最後一個元素的時候:
>>> s = 'abcde' >>> for i in [None] + range(-1, -len(s), -1): ... print s[:i] ... abcde abcd abc ab a
如今這個程序符合咱們的要求了。彷佛還能夠先建立一個只包含None 的列表,而後用extend()函數把range()的輸出添加到這個列表,或者先創建range()輸出組成的列表而後再把None 插入到這個列表的最前面,而後對這個列表進行遍歷,可是可變對象的內建函數extend()根本就沒有返回值,因此這個方法是行不通的:
>>> for i in [None].extend(range(-1, -len(s), -1)): ... print s[:i] ... Traceback (most recent call last): File "<stdin>", line 1, in ? TypeError: iteration over non-sequence
這個錯誤發生的緣由是[None].extend(...)函數返回None , None 既不是序列類型也不是可迭代對象.
序列自己就內含了迭代的概念,由於迭代這個概念就是從序列,迭代器,或者其餘支持迭代操做的對象中泛化得來的。因爲Python 的for 循環能夠遍歷全部的可迭代類型,在(非純序列對象上)執行for 循環時就像在一個純序列對象上執行同樣。並且Python 的不少原來只支持序列做爲參數的內建函數如今也開始支持迭代器或者或類迭代器了.咱們把這些類型統稱爲"可迭代對象".在這一章裏咱們會詳細的討論跟序列關係緊密的內建函數(BIF).
類型轉換
內建函數list(),str()和tuple()被用作在各類序列類型之間轉換。你能夠把它們理解成其餘語言裏面的類型轉換,可是並無進行任何的轉換。這些轉換其實是工廠函數(在第4章介紹),將對象做爲參數,並將其內容(淺)拷貝到新生成的對象中.表6.2 列出了適用於序列類型的轉換函數。
咱們又用了一次「轉換」這個詞。不過,爲何Python 裏面不簡單地把一個對象轉換成另外一個對象呢?回過頭看一下第4 章就會知道,一旦一個Python 的對象被創建,咱們就不能更改其身份或類型了.若是你把一個列表對象傳給list()函數,便會建立這個對象的一個淺拷貝,而後將其插入新的列表中。一樣地,在作鏈接操做和重複操做時,咱們也會這樣處理。所謂淺拷貝就是隻拷貝了對對象的索引,而不是從新創建了一個對象!若是你想徹底的拷貝一個對象(包括遞歸,若是你的對象是一個包含在容器中的容器),你須要用到深拷貝,關於淺拷貝和深拷貝的更多信息會在本章的末尾講到。
str()函數在須要把一個對象的可打印信息輸出時特別有用,不只僅是對序列類型,對其餘類型的對象一樣如此.
Unicode()是str()函數的unicode 版本,它跟str()函數基本同樣.
list()和tuple()函數在列表類型和元組類型的互換時很是有用.不過,雖然這些函數也適用於string類型(由於string 類型也是序列的一種),可是在string 類型上應用tuple()和list()函數卻得不到咱們一般但願的結果.
Operational
咱們將分別在每一個序列的章節裏面提供使用這些函數的例子.
Python 裏面單引號和雙引號的做用是相同的。Python 用"原始字符串"操做符來建立直接量字符串,因此再作區分就沒什麼意義了.
幾乎全部的Python 應用程序都會某種方式用到字符串類型.字符串是一種直接量或者說是一種標量,這意味着Python 解釋器在處理字符串時是把它做爲單一值而且不會包含其餘Python 類型的。字符串是不可變類型,就是說改變一個字符串的元素須要新建一個新的字符串.
根據在2.2 章節裏面對類型和類的概念進行的統一,Python 實際上有3 類字符串.一般意義的字符串(str)和Unicode 字符串(unicode)實際上都是抽象類basestring 的子類.這個basestring 是不能實例化的,若是你試圖實例化一個basestring 類,你會獲得如下報錯信息:
>>> basestring('foo') Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: The basestring type cannot be instantiated
字符串的建立和賦值
建立一個字符串就像使用一個標量同樣簡單,固然你也能夠把str()做爲工廠方法來建立一個字符串並把它賦值給一個變量:
>>> aString = 'Hello World!' # 使用單引號 >>> anotherString = "Python is cool!" # 使用雙引號 >>> print aString # print 不帶引號的 Hello World! >>> anotherString # 不是進行print 操做,帶有引號 'Python is cool!' >>> s = str(range(4)) # 把一個列表轉換成一個字符串 >>> s '[0, 1, 2, 3]'
如何訪問字符串的值(字符和子串)
Python 裏面沒有字符這個類型,而是用長度爲1 的字符串來表示這個概念,固然,這其實也是一個子串。
如何改變字符串
你能夠經過給一個變量賦值(或者重賦值)的方式「更新」一個已有的字符串。
>>> aString = aString[:6] + 'Python!' >>> aString 'Hello Python!' >>> aString = 'different string altogether' >>> aString 'different string altogether'
跟數字類型同樣,字符串類型也是不可變的,因此你要改變一個字符串就必須經過建立一個新串的方式來實現。也就是說你不能只改變一個字符串的一個字符或者一個子串,然而,經過拼湊一個舊串的各個部分來獲得一個新串是被容許的,正如上面你看到的那樣.
如何刪除字符和字符串
再重複一遍,字符串是不可變的,因此你不能僅僅刪除一個字符串裏的某個字符,你能作的是清空一個空字符串,或者是把剔除了不須要的部分後的字符串組合起來造成一個新串。假設你想要從"Hello World!"裏面刪除小寫的'l'
>>> aString = 'Hello World!' >>> aString = aString[:3] + aString[4:] >>> aString 'Helo World!'
經過賦一個空字符串或者使用del 語句來清空或者刪除一個字符串:
>>> aString = '' >>> aString '' >>> del aString
在大部分應用程序裏,沒有必要顯式的刪除字符串。定義這個字符串的代碼最終會結束,那時Python 會自動釋放這些字符串.
在第4 章裏面,咱們介紹了一些適用於包括標準類型在內的大部分對象的操做符,在這裏再看一下這些其中的一些操做符是怎樣做用於字符串類型的,下面是幾個簡單的例子:
>>> str1 = 'abc' >>> str2 = 'lmn' >>> str3 = 'xyz' >>> str1 < str2 True >>> str2 != str3 True >>> str1 < str3 and str2 == 'xyz' False
在作比較操做的時候,字符串是按照ASCII 值的大小來比較的.
切片( [ ] 和 [ : ] )
正向索引
反向索引
默認索引
接下來以字符串'abcd'爲例子.能夠用長度操做符來確認該字符串的長度是4:
>>> aString = 'abcd' >>> len(aString) 4
用一個參數來調用切片操做符結果是一個單一字符,而使用一個數值範圍(用':')做爲參數調用切片操做的參數會返回一串連續地字符.再強調一遍,對任何範圍[start:end],咱們能夠訪問到包括start 在內到end(不包括end)的全部字符,
>>> aString[0] 'a' >>> aString[1:3] 'bc' >>> aString[2:4] 'cd' >>> aString[4] Traceback (innermost last): File "<stdin>", line 1, in ? IndexError: string index out of range
使用不在容許範圍(本例中是0 到3)內的索引值會致使錯誤。上面的aString[2:4]卻並無出錯,那是由於實際上它返回的是索引值2 和3 的值。可是直接拿4 做爲索引訪問是不被容許的。
在進行反向索引操做時,是從-1 開始,向字符串的開始方向計數,到字符串長度的負數爲索引的結束:
>>> aString[-1] 'd' >>> aString[-3:-1] 'bc' >>> aString[-4] 'a'
若是開始索引或者結束索引沒有被指定,則分別以字符串的第一個和最後一個索引值爲默認值。
>>> aString[2:] 'cd' >>> aString[1:] 'bcd' >>> aString[:-1] 'abc' >>> aString[:] 'abcd'
注意:起始/結束索引都沒有指定的話會返回整個字符串.
【其實感受沒有指定的話,應該是最後一個索引值+1,由於若是以最後一個索引值爲默認的話,應該不會顯示d,按照切片的定義來講】
成員操做符(in ,not in)
成員操做符用於判斷一個字符或者一個子串(中的字符)是否出如今另外一個字符串中。注意,成員操做符不是用來判斷一個字符串是否包含另外一個字符串的【誰能告訴我這個啥意思,示例裏面不就是判斷一個字符串包含一個字符串了嗎】 ,這樣的功能由find()或者index()(還有它們的兄弟:rfind()和rindex())函數來完成)
下面是一些字符串和成員操做符的例子.
>>> 'bc' in 'abcd' True >>> 'n' in 'abcd' False >>> 'nm' not in 'abcd' True
在例6.1 裏面,咱們會用到下面這些string 模塊預約義的字符串:
>>> import string >>> string.ascii_uppercase 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' >>> string.ascii_lowercase 'abcdefghijklmnopqrstuvwxyz' >>> string.ascii_letters 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' >>> string.digits '0123456789'
例 6.1 標識符檢查 (idcheck.py)標識符合法性檢查。這個小例子只檢查長度大於等於2 的標識符.
# 導入string 模塊而且預約義了兩個字符串 import string alphas = string.ascii_letters + '_' nums = string.digits inp = input('Identifier to test? ') if len(inp) > 1: if inp[0] not in alphas: print ("invalid first symbol") else: for otherChar in inp[1:]: if otherChar not in alphas +nums: print ("invalid other char") break print ("ok") else: print ("len > 2")
這個例子還展現了字符串鏈接符( + )的使用。
核心提示: 性能
從性能的的角度來講,把重複操做做爲參數放到循環裏面進行是很是低效的.
while i < len(myString): print 'character %d is:', myString[i]
上面的循環操做把大把的時間都浪費到了重複計算字符串myString 的長度上了.每次循環迭代都要運行一次這個函數.若是把這個值作一次保存,咱們就能夠用更爲高效的方式重寫咱們的循環操做.
length = len(myString) while i < length: print 'character %d is:', myString[i]
這個方法一樣適用於上面的例6.1
for otherChar in myInput[1:]: if otherChar not in alphas + nums:
在這個if 裏面執行了合併兩個字符串的操做。被合併的這兩個字符串從始至終就沒變過,而每次都會從新進行一次計算.若是先把這兩個字符串存爲一個新字符串,咱們就能夠直接引用這個字符串而不用進行重複計算了。
alphnums = alphas + nums for otherChar in myInput[1:]: if otherChar not in alphnums:
這段程序並無想到Python 的關鍵字,而這些都是做爲保留字,不容許用作標識符的.咱們把這做爲課後做業留給讀者(見練習6-2)。
鏈接符( + )
運行時刻字符串鏈接,咱們能夠經過鏈接操做符來從原有字符串得到一個新的字符串.eg:
>>> 'Spanish' + 'Inquisition' 'SpanishInquisition' >>> 'Spanish' + ' ' + 'Inquisition' 'Spanish Inquisition' >>> s = 'Spanish' + ' ' + 'Inquisition' + ' Made Easy' >>> s 'Spanish Inquisition Made Easy' >>> import string >>> string.upper(s[:3] + s[20]) # archaic (see below) 'SPAM'
最後一個例子展現了用一個字符串s 的兩個切片來構成一個新串的操做,,該方法負責把字符串的全部字符都變爲大寫。
String 模塊的方法是在Python1.6 裏面添加進來的,因此這個操做也能夠用最後一個字符串的一個單一方法調用來完成。如今已經沒有必要導入string 模塊了,除非你須要訪問該模塊本身定義的字符串常量。出於性能方面方面,建議不要用string 模塊。緣由是Python 必須爲每個參加鏈接操做的字符串分配新的內存,包括新產生的字符串。推薦使用字符串格式化操做符(%),或者把全部的字符串放到一個列表中去,而後用一個join()方法來把它們鏈接在一塊兒。
>>> '%s %s' % ('Spanish', 'Inquisition') 'Spanish Inquisition' >>> >>> s = ' '.join(('Spanish', 'Inquisition', 'Made Easy')) >>> s 'Spanish Inquisition Made Easy' >>> >>> # no need to import string to use string.upper(): >>> ('%s%s' % (s[:3], s[20])).upper() 'SPAM'
編譯時字符串鏈接
上面的語法在運行時字符串鏈接的加法操做,這個用法是很是標準的。Python 中還有一種並非常常用到,更像是一種程序員的習慣用法的語法.Python 的語法容許你在源碼中把幾個字符串連在一塊兒寫,以此來構建新字符串:
>>> foo = "Hello" 'world!' >>> foo 'Helloworld!'
經過這種方法,你能夠把長的字符串分紅幾部分來寫,而不用加反斜槓。
你能夠在一行裏面混用兩種分號。這種寫法的好處是你能夠把註釋也加進來,以下:
>>> f = urllib.urlopen('http://' # protocol ... 'localhost' # hostname ... ':8000' # port ... '/cgi-bin/friends2.py') # file
如你所想,下面就是urlopen()方法所獲得的真實輸入:
>>> 'http://' 'localhost' ':8000' '/cgi-bin/friends2.py' 'http://localhost:8000/cgi-bin/friends2.py'
普通字符串轉化爲Unicode 字符串
若是把一個普通字符串和一個Unicode 字符串作鏈接處理,Python 會在鏈接操做前先把普通字符串轉化爲Unicode 字符串:
>>> 'Hello' + u' ' + 'World' + u'!' u'Hello World!'
重複操做符( * )
重複操做符建立一個包含了原有字符串的多個拷貝的新串:
>>> 'Ni!' * 3 'Ni!Ni!Ni!' >>> '*'*40 '****************************************' >>> print '-' * 20, 'Hello World!', '-' * 20 -------------------- Hello World! -------------------- >>> who = 'knights' >>> who * 2 'knightsknights' >>> who 'knights'
像其餘的標準操做符同樣,原變量是不被修改的,就像上面最後一個例子所示。
只適用於字符串類型,而且支持全部printf()式的格式化操做.語法以下:
左邊的format_string 裏面一般會在printf()函數的第一個參數裏面見到的同樣:包含%的格式化字符串.arguments_to_convert 參數是你要轉化、顯示的變量,對應於你送給prinf 的其餘參數.
表6.4 列出了可用的各類符號.
Python 支持兩種格式的輸入參數:元組和字典。字典實際上是一個哈希鍵-值對的集合。這種形式裏面,key 是做爲格式字符串出現,相對應的value 值做爲參數在進行轉化時提供給格式字符串.
格式字符串既能夠跟print 語句一塊兒用來向終端用戶輸出數據,又能夠用來合併字符串造成新字符串,並且還能夠直接顯示到GUI(Graphical User Interface)界面上去.
表6.5 格式化操做符輔助指令
如下是一些使用格式字符串的例子:
十六進制輸出: >>> "%x" % 108 '6c' >>> "%X" % 108 '6C' >>> "%#X" % 108 '0X6C' >>> "%#x" % 108 '0x6c' 浮點數和科學記數法形式輸出: >>> '%f' % 1234.567890 '1234.567890' >>> '%.2f' % 1234.567890 '1234.57' >>> '%E' % 1234.567890 '1.234568E+03' >>> '%e' % 1234.567890 '1.234568e+03' >>> '%g' % 1234.567890 '1234.57' >>> '%G' % 1234.567890 '1234.57' >>> "%e" % (1111111111111111111111L) '1.111111e+21' 整數和字符串輸出: >>> "%+d" % 4 '+4' >>> "%+d" % -4 '-4' >>> "we are at %d%%" % 100 'we are at 100%' >>> 'Your host is: %s' % 'earth' 'Your host is: earth' >>> 'Host: %s\tPort: %d' % ('mars', 80) 'Host: mars Port: 80' >>> num = 123 >>> 'dec: %d/oct: %#o/hex: %#X' % (num, num, num) 'dec: 123/oct: 0173/hex: 0X7B' >>> "MM/DD/YY = %02d/%02d/%d" % (2, 15, 67) 'MM/DD/YY = 02/15/67' >>> w, p = 'Web', 'page' >>> 'http://xxx.yyy.zzz/%s/%s.html' % (w, p) 'http://xxx.yyy.zzz/Web/page.html'
上面的例子都是使用的元組類型的參數做轉換.下面咱們將把字典類型的參數提供給格式化操做符.
>>> 'There are %(howmany)d %(lang)s Quotation Symbols' % \ ... {'lang': 'Python', 'howmany': 3} 'There are 3 Python Quotation Symbols'
字符串格式化操做符是一個很是有用的調試工具。事實上,全部的Python 對象都有一個字符串表示形式(經過repr()函數,'' 或str()函數來展示).print 語句自動爲每一個對象調用str()函數.更好的是,在定義本身的對象時,你能夠利用"鉤子"爲你的對象建立字符串表達形式. 這樣,repr(),str()或`` 或者print 被調用時,就能夠得到一個適當的字符串描述信息.即便在壞的不能再壞的狀況下,repr()或者str()也不能顯示一個對象的信息時,Pythonic 方式的默認作法最起碼能給你返回想以下格式的信息:
<... something that is useful ...>.
程序員會偶爾出現遺漏轉換類型符號的錯誤,好比說,用了%(lang)而不是正確的%(lang)s.爲了保證字符串被正確的轉換,程序員必須明確的記住轉換類型參數,好比究竟是要轉成字符串,整數仍是其餘什麼類型.
新式的字符串模板的優點是不用去記住全部的相關細節的,而是像如今shell 風格的腳本語言裏面那樣使用美圓符號($).
因爲新式的字符串Template 對象的引進使得string 模塊又從新活了過來,Template 對象有兩個方法,substitute()和safe_substitute().前者更爲嚴謹,在key 缺乏的狀況下它會報一個KeyError 的異常出來,然後者在缺乏key 時,直接原封不動的把字符串顯示出來.
>>> from string import Template >>> s = Template('There are ${howmany} ${lang} Quotation Symbols') >>> print s.substitute(lang='Python', howmany=3) There are 3 Python Quotation Symbols >>> print s.substitute(lang='Python') Traceback (most recent call last): File "<stdin>", line 1, in ? File "/usr/local/lib/python2.4/string.py", line 172, in substitute return self.pattern.sub(convert, self.template) File "/usr/local/lib/python2.4/string.py", line 162, in convert val = mapping[named] KeyError: 'howmany' >>> print s.safe_substitute(lang='Python') There are ${howmany} Python Quotation Symbols
新式的字符串模板是從Python2.4 開始加入的,更多信息請查閱Python 類庫手冊和PEP292.
關於原始字符串的目的,是爲了對付那些在字符串中出現的特殊字符(下面的小節會介紹這些特殊字符)。在原始字符串裏,全部的字符都是直接按照字面的意思來使用,沒有轉義特殊或不能打印的字符。
原始字符串的這個特性讓一些工做變得很是的方便,好比正則表達式的建立(詳見文檔的re模塊).正則表達式是一些定義了高級搜索匹配方式的字符串,一般是由表明字符,分組、匹配信息、變量名、和字符類等的特殊符號組成。正則表達式模塊已經包含了足夠用的符號。但當你必須插入額外的符號來使特殊字符表現的像普通字符的時候,你就陷入了「字符數字」的泥潭!這時原始字符串就會派上用場了.除了原始字符串符號(引號前面的字母"r")之外,原始字符串跟普通字符串有着幾乎徹底相同的語法.
這個'r'能夠是小寫也能夠是大寫,惟一的要求是必須緊靠在第一個引號前.
在三個例子的第一個例子裏面,咱們須要一個反斜槓加一個'n'來而不是一個換行符.:
>>> '\n' '\n' >>> print '\n' >>> r'\n' '\\n' >>> print r'\n' \n
接下來的例子裏,咱們打不開咱們的README 文件了,爲何?由於'\t'和'\r'被當成不在咱們的文件名中的特殊符號,但它們實際上文件路徑的中4 個獨立的字符.
>>> f = open('C:\windows\temp\readme.txt', 'r') Traceback (most recent call last): File "<stdin>", line 1, in ? f = open('C:\windows\temp\readme.txt', 'r') IOError: [Errno 2] No such file or directory: 'C:\\win- dows\\temp\readme.txt' >>> f = open(r'C:\windows\temp\readme.txt', 'r') >>> f.readline() 'Table of Contents (please check timestamps for last update!)\n' >>> f.close()
最後咱們要找一對原始的\n 字符而不是換行。爲了找到它,咱們使用了一個簡單的正則表達式,它的做用是查找一般被用來表示空白字符的反斜線-字符對(backslash-characterpairs)。
>>> import re >>> m = re.search('\\[rtfvn]', r'Hello World!\n') >>> if m is not None: m.group() ... >>> m = re.search(r'\\[rtfvn]', r'Hello World!\n') >>> if m is not None: m.group() ... '\\n'
Unocide 字符串操做符,大寫的(U)和小寫的(u)是和 Unicode 字符串一塊兒被引入的. 它用來把標準字符串或者是包含Unicode 字符的字符串轉換成徹底地Unicode 字符串對象。關於Unicode 字符串的進一步信息在6.7.4 節有詳細介紹.另外,字符串方法(見6.6節)和正則表達式引擎也支持Unicode.下面是幾個例子:
u'abc' U+0061 U+0062 U+0063 u'\u1234' U+1234 u'abc\u1234\n' U+0061 U+0062 U+0063 U+1234 U+0012
Unicode 操做符也能夠接受原始Unicode 字符串, 只要咱們將Unicode 操做符和原始字符串操做符鏈接在一塊兒就能夠了. 注意:Unicode 操做符必須出如今原始字符串操做符前面.
ur'Hello\nWorld!'
cmp() 內建的cmp()函數也根據字符串的ASCII 碼值進行比較.
>>> str1 = 'abc' >>> str2 = 'lmn' >>> str3 = 'xyz' >>> cmp(str1, str2) -11 >>> cmp(str3, str1) 23 >>> cmp(str2, 'lmn') 0
內建函數len()返回字符串的字符數.
max() and min() >>> str2 = 'lmn' >>> str3 = 'xyz' >>> max(str2) 'n' >>> min(str3) 'x'
雖然max()和min()函數對其餘的序列類型可能更有用,但對於string 類型它們能很好地運行,返回最大或者最小的字符,(按照ASCII 碼值排列),下面是幾個例子:
>>> min('ab12cd') '1' >>> min('AB12CD') '1' >>> min('ABabCDcd') 'A'
enumerate()
>>> s = 'foobar' >>> for i, t in enumerate(s): ... print i, t ... 0 f 1 o 2 o 3 b 4 a 5 r
zip()
>>> s, t = 'foa', 'obr' >>> zip(s, t) [('f', 'o'), ('o', 'b'), ('a', 'r')]
raw_input()
內建的raw_input()函數使用給定字符串提示用戶輸入並將這個輸入返回,eg:
>>> user_input = raw_input("Enter your name: ") Enter your name: John Doe >>> user_input 'John Doe' >>> len(user_input) 8
Python 裏面沒有C 風格的結束字符NULL,你輸入多少個字符,len()函數的返回值就是多少.
str() and unicode()
str()和unicode()函數都是工廠函數,就是說產生所對應的類型的對象.它們接受一個任意類型的對象,而後建立該對象的可打印的或者Unicode 的字符串表示. 它們和basestring 均可以做爲參數傳給isinstance()函數來判斷一個對象的類型.
>>> isinstance(u'\0xAB', str) False >>> not isinstance('foo', unicode) True >>> isinstance(u'', basestring) True >>> not isinstance('foo', basestring) False
chr(), unichr(), and ord()
chr()函數用一個範圍在range(256)內的(就是0 到255)整數作參數,返回一個對應的字符.
unichr()跟它同樣,只不過返回的是Unicode 字符,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 定義範圍,則會引起一個TypeError 的異常。
>>> chr(65) 'A' >>> ord('a') 97 >>> unichr(12345) u'\u3039' >>> chr(12345) Traceback (most recent call last): File "<stdin>", line 1, in ? chr(12345) ValueError: chr() arg not in range(256) >>> ord(u'\ufffff') Traceback (most recent call last): File "<stdin>", line 1, in ? ord(u'\ufffff') TypeError: ord() expected a character, but string of length 2 found >>> ord(u'\u2345') 9029
這些方法實現了string 模塊中的大部分方法,表6.6 列出了目前字符串內建支持的方法,全部這些方法都包含了對Unicode 的支持,有一些甚至是專門用於Unicode 的.
幾個使用字符串方法的例子:
>>> quest = 'what is your favorite color?' >>> quest.capitalize() 'What is your favorite color?' >>> quest.center(40) ' what is your favorite color? ' >>> quest.count('or') 2 >>> quest.endswith('blue') False >>> quest.endswith('color?') True >>> quest.find('or', 30) -1 >>> quest.find('or', 22) 25 >>> quest.index('or', 10) 16 >>> ':'.join(quest.split()) 'what:is:your:favorite:color?' >>> quest.replace('favorite color', 'quest') 'what is your quest?' >>> quest.upper() 'WHAT IS YOUR FAVORITE COLOR?'
上面最複雜的例子是有split()和join()函數的那個.首先咱們在string 上調用split()函數,沒有用參數,也就是說以空格做爲分隔符分隔字符串,而後咱們以這個包含單詞的列表作參數調用join()方法把這些單詞用一個新的分隔符冒號從新串在一塊兒,注意,咱們首先用split()函數把string 切片成一個列表,而後咱們在字符串':'上應用join()方法把這個列表從新鏈接成一個字符串.
一個反斜線加一個單一字符能夠表示一個特殊字符,一般是一個不可打印的字符,這就是咱們上面討論的特殊字符。
除了一般用的特殊字符,好比換行符(\n),tab 符(\t)以外,也能夠直接用ASCII 碼值來標示特殊字符:\000 或者\xXX,分別對應字符的八進制和十六進制ASCII 碼值,下面分別是十進制,八進制和十六進制的0,65,和255:
特殊字符,包括反斜槓轉義的那些均可以像普通字符同樣存儲到Python 的字符串中.跟C 字符串的另外一個不一樣之處是Python 的字符串並非以NUL(\000)做爲結束符的.NUL 跟其餘的反斜槓轉義字符沒什麼兩樣.事實上,一個字符串中不只能夠出現NUL 字符,並且還能夠出現不止一次,在字符串的任意位置均可以。表6.7 列出了被大部分Python 版本支持的轉義字符.
如上所述,就像使用連字符來讓一行的內容持續到下一行同樣,能夠用顯式定義八進制或者十六進制的ASCII 碼的方式定義特殊字符,合法的ASCII 碼值範圍是從0 到255(八進制的是0177,十六進制是0XFF).
Table 6.7 反斜槓開頭的轉義字符
控制字符的一個做用是用作字符串裏面的定界符,在數據庫或者web 應用中,大多數的可打印字符都是被容許用在數據項裏面的,就是說可打印的字符不適合作定界符.用可打印的字符串好比冒號(:)來做定界符,將會很難分辨一個字符究竟是數據仍是定界符.並且還會限定你能用在數據項裏面的字符數量,而這不是你想要的.一個一般的解決方法是,使用那些不常用的,不可打印的ASCII 碼值來做爲定界符,它們是很是完美的定界符,這樣一來諸如冒號這樣的可打印字符就能夠解脫出來用在數據項中了.
雖然你能夠用單引號或者雙引號來定義字符串,可是若是你須要包含諸如換行符這樣的特殊字符時,單引號或者雙引號就不是那麼方便了。Python 的三引號就是爲了解決這個的,它容許一個字符串跨多行,字符串中能夠包含換行符、製表符以及其餘特殊字符.三引號的語法是一對連續的單引號或者雙引號(一般都是成對的用):
>>> hi = '''hi there''' >>> hi # repr() 'hi\nthere' >>> print hi # str() hi there
三引號讓程序員從引號和特殊字符串的泥潭裏面解脫出來,自始至終保持一小塊字符串的格式是所謂的WYSIWYG(所見即所得)格式的。
一個典型的用例是,當你須要一塊HTML 或者SQL 時,這時用字符串組合,特殊字符串轉義將會很是的繁瑣.
errHTML = ''' <HTML><HEAD><TITLE> Friends CGI Demo</TITLE></HEAD> <BODY><H3>ERROR</H3> <B>%s</B><P> <FORM><INPUT TYPE=button VALUE=Back ONCLICK="window.history.back()"></FORM> </BODY></HTML> ''' cursor.execute(''' CREATE TABLE users ( login VARCHAR(8), uid INTEGER, prid INTEGER) ''')
Python 替你管理內存,每次你修改一個字符串或者作一些改變字符串內容的操做時,Python 都會自動爲你分配一個新串.在下面的例子裏面,Python 分別爲"abc"和"def"分配了空間,當進行鏈接操做時,Python 自動爲新的字符串"abcdef"分配了空間.
>>> 'abc' + 'def' 'abcdef' 給變量賦值是沒什麼不一樣: >>> s = 'abc' >>> s = s + 'def' >>> s 'abcdef'
上面的例子裏,看起來是咱們先把"abc"賦給了s,而後在s 的末尾添加了"def".這樣看起來字符串彷佛是可變的,其實事實是在"s+'def""這個操做進行的時候,新建了一個新字符串,而後這個新的對象被賦給了s,原來的字符串'abc'被析構掉了.
咱們能夠用id()函數來更明顯的顯示出來到底發生了什麼:
>> s = 'abc' >>> id(s) 135060856 >>> s += 'def' >>> id(s) 135057968
注意修改先後的身份是不一樣的.另外一個測試是針對字符串的一個字符或者一個子串所作的修改.咱們如今將展現對字符串的一個字符或者一片字符的改動都是不被容許的:
>>> s 'abcdef' >>> s[2] = 'C' Traceback (innermost last): File "<stdin>", line 1, in ? AttributeError: __setitem__ >>> s[3:6] = 'DEF' Traceback (innermost last): File "<stdin>", line 1, in ? AttributeError: __setslice__
兩個操做都拋出了異常.爲了實現要求,咱們須要用現有字符串的子串來構建一個新串,而後把這個新串賦給原來的變量:
>>> s 'abcdef' >>> >>> s = '%sC%s' % (s[0:2], s[3:]) >>> s 'abCdef' >>> s[0:3] + 'DEF' 'abCDEF'
對像字符串這樣的不可變對象,左值必須是一個完整的對象,好比說一個字符串對象,不能是字符串的一部分.對賦值操做的右值沒有這個限制.
從Python1.6 起引進的Unicode 字符串支持,是用來在多種雙字節字符的格式、編碼進行轉換的,其中包括一些對這類字符串的操做管理功能。內建的字符串和正則表達式對Unicode 字符串的支持,再加上string 模塊的輔助,Python 已經能夠應付大部分應用對Unicode 的存儲、訪問、操做的須要了。
Unicode 是計算機能夠支持這個星球上多種語言的祕密武器.在Unicode 以前,用的都是ASCII,ASCII 碼很是簡單,每一個英文字符都是以七位二進制數的方式存貯在計算機內,其範圍是32 到126.當用戶在文件中鍵入一個大寫字符A 時,計算機會把A 的ASCII 碼值65寫入磁盤,而後當計算機讀取該文件時,它會首先把65 轉化成字符A 而後顯示到屏幕上.ASCII 編碼的文件小巧易讀。一個程序只需簡單地把文件的每一個字節讀出來,把對應的數值轉換成字符顯示出來就能夠了.可是ASCII 字符只能表示95 個可打印字符.後來的軟件廠商把ASCII 碼擴展到了8 位,這樣一來它就能夠多標識128 個字符,但是223 個字符對須要成千上萬的字符的非歐洲語系的語言來講仍然太少。Unicode 經過使用一個或多個字節來表示一個字符的方法突破了ASCII 的限制. 在這樣機制下, Unicode 能夠表示超過90,000 個字符.
早先,Python 只能處理8 位的ASCII 值,字符串就是簡單的數據類型,爲了處理一個字符串,用戶必須首先建立一個字符串,而後把它做爲參數傳給string 模塊的一個函數來處理.
2000年,Python1.6(和2.0)版釋出,Unicode 第一次在Python 裏面獲得了支持.
爲了讓Unicode 和ASCII 碼值的字符串看起來儘量的相像,Python 的字符串從原來的簡單數據類型改爲了真正的對象.ASCII 字符串成了StringType,而Unicode 字符串成了UnicodeType 類型.它們的行爲是很是相近的.string 模塊裏面都有相應的處理函數.string 模塊已經中止了更新,只保留了ASCII 碼的支持,string 模塊已經不推薦使用,在任何須要跟Unicode 兼容的代碼裏都不要再用該模塊,Python 保留該模塊僅僅是爲了向後兼容。
Python 裏面處理Unicode 字符串跟處理ASCII 字符串沒什麼兩樣.Python 把硬編碼的字符串叫作字面上的字符串,默認全部字面上的字符串都用ASCII 編碼,能夠經過在字符串前面加一個'u'前綴的方式聲明Unicode 字符串,這個'u'前綴告訴Python 後面的字符串要編碼成Unicode字符串 .
>>> "Hello World" # ASCII string >>> u"Hello World" # Unicode string
內建的str()函數和chr()函數並無升級成能夠處理Unicode.它們只能處理常規的ASCII 編碼字符串,若是一個Unicode 字符串被做做爲參數傳給了str()函數,它會首先被轉換成ASCII 字符串而後在交給str()函數.若是該Unicode 字符串中包含任何不被ASCII 字符串支持的字符,會致使str()函數報異常.一樣地,chr()函數只能以0 到255 做爲參數工做.若是你傳給它一個超出此範圍的值(好比說一個Unicode 字符),它會報異常.新的內建函數unicode()和unichar()能夠當作Unicode 版本的str()和chr().Unicode()函數能夠把任何Python 的數據類型轉換成一個Unicode 字符串,若是是對象,而且該對象定義了__unicode__()方法,它還能夠把該對象轉換成相應的Unicode 字符串.具體內容見6.1.3 和6.5.3 章節.
codec 是COder/DECoder 的首字母組合.它定義了文本跟二進制值的轉換方式,跟ASCII 那種用一個字節把字符轉換成數字的方式不一樣,Unicode 用的是多字節.這致使了Unicode 支持多種不一樣的編碼方式. 好比說codec 支持的四種耳熟能詳的編碼方式是 :ASCII,ISO8859-1/Latin-1,UTF-8 和UTF-16.中最著名的是UTF-8 編碼,它也用一個字節來編碼ASCII 字符,這讓那些必須同時處理ASCII碼和Unicode 碼文本的程序員的工做變得很是輕鬆,由於ASCII 字符的UTF-8 編碼跟ASCII 編碼徹底相同。
UTF-8 編碼能夠用1 個到4 個字節來表示其餘語言的字符,CJK/East 這樣的東亞文字通常都是用3 個字節來表示,那些少用的、特殊的、或者歷史遺留的字符用4 個字節來表示.這給那些須要直接處理Unicode 數據的程序員帶來了麻煩,由於他們沒有辦法按照固定長度逐一讀出各個字符.幸運的是咱們不須要掌握直接讀寫Unicode 數據的方法,Python 已經替咱們完成了相關細節,咱們無須爲處理多字節字符的複雜問ti而擔憂.Python 裏面的其餘編碼不是很經常使用,事實上,咱們認爲大部分的Python 程序員根本就用不着去處理其餘的編碼,UTF-16 多是個例外.
UTF-16 多是之後大行其道的一種編碼格式,它容易讀寫,由於它把全部的字符都是用單獨的一個16 位字,兩個字節來存儲的,正由於此,這兩個字節的順序須要定義一下,通常的UTF-16 編碼文件都須要一個BOM(Byte Order Mark),或者你顯式地定義UTF-16-LE(小端)或者UTF-16-BE(大端)字節序.
從技術上講,UTF-16 也是一種變長編碼,但它不是很經常使用(人們通常不會知道或者根本不在乎除了基本多文種平面BMP 以外到底使用的是那種平面),儘管如此,UTF-16 並不向後兼容ASCII,所以,實現它的程序不多,由於你們須要對ASCII 進行支持。
Unicode 支持多種編碼格式,這爲程序員帶來了額外的負擔,每當你向一個文件寫入字符串的時候,你必須定義一個編碼(encoding 參數)用於把對應的Unicode內容轉換成你定義的格式,Python 經過Unicode 字符串的encode()函數解決了這個問ti,該函數接受字符串中的字符爲參數,輸出你指定的編碼格式的內容。
因此,每次咱們寫一個Unicode 字符串到磁盤上咱們都要用指定的編碼器給他"編碼"一下,相應地,當咱們從這個文件讀取數據時,咱們必須"解碼"該文件,使之成爲相應的Unicode 字符串對象.
簡單的例子(uniFile.py):下面的代碼建立了一個Unicode 字符串,用UTF-8 編碼器將它編碼,而後寫入到一個文件中去.接着把數據從文件中讀回來,解碼成Unicode 字符串對象.最後,打印出Unicode 字符串,用以確認程序正確地運行.
CODEC = 'utf-8' FILE = 'unicode.txt' hello_out = u"Hello world\n" #建立了一個Unicode 字符串 bytes_out = hello_out.encode(CODEC) #指定的編碼格式對其進行編碼 f = open(FILE, 'w') f.write(bytes_out) f.close() f = open(FILE, 'r') bytes_in = f.read() f.close() hello_in = bytes_in.decode(CODEC) # 解碼 print (hello_in,)
運行
$ unicode_example.py Hello World
在文件系統中也會發現一個叫unicode.txt 的文件,裏面包含跟輸出的內容一致的數據.
$ cat unicode.txt Hello World!
簡單Web 例子:在第20 章Web 編程裏面咱們展現了一個簡單的在CGI 應用中使用Unicode 的例子.
這些處理Unicode 字符串的例子簡單到讓人感到有點假,事實上,只要你遵照如下的規則,處理Unicode 就是這麼簡單:
程序中出現字符串時必定要加個前綴 u.
不要用 str()函數,用unicode()代替.
不要用過期的 string 模塊 -- 若是傳給它的是非ASCII 字符,它會把一切搞砸。
不到必須時不要在你的程序裏面編解碼 Unicod 字符.只在你要寫入文件或數據庫或者網絡時,才調用encode()函數;相應地,只在你須要把數據讀回來的時候才調用decode()函數.
這些規則能夠規避90%因爲Unicode 字符串處理引發的bug.可是剩下的10%的問ti卻讓你處理不了,幸好Python 提供了大量的模塊、庫來替你處理這些問ti.它們可讓你用10 行Python 語句寫出其餘語言須要100 行語句才能完成的功能,可是相應地,對Unicode 支持的質量也徹底取決於這些模塊、庫.Python 標準庫裏面的絕大部分模塊都是兼容Unicode 的.除了pickle 模塊!pickle 模塊只支持ASCII 字符串。若是你把一個Unicode 字符串交給pickle 模塊來unpickle,它會報異常.
你必須先把你的字符串轉換成ASCII 字符串才能夠.因此最好是避免基於文本的pickle 操做.幸運地是如今二進制格式已經做爲pickle 的默認格式了,pickle 的二進制格式支持不錯.這點在你向數據庫裏面存東西是尤其突出,把它們做爲BLOB 字段存儲而不是做爲TEXT 或者VARCHAR字段存儲要好不少.萬一有人把你的字段改爲了Unicode 類型,這能夠避免pickle 的崩潰.
若是你的程序裏面用到了不少第三方模塊,那麼你極可能在各個模塊統一使用Unicode 通信方面遇到麻煩,Unicode 還沒成爲一項必須的規定,在你係統裏面的第三方模塊(包括你的應用要面對的平臺\系統)須要用相同的Unicode 編碼,不然,可能你就不能正確的讀寫數據.
做爲一個例子,假設你正在構建一個用數據庫來讀寫Unicode 數據的Web 應用.爲了支持Unicode,你必須確保如下方面對Unicode 的支持:
數據庫服務器(MySQL,PostgreSQL,SQL Server,等等)
數據庫適配器(MySQLdb 等等)
Web 開發框架(mod_python,cgi,Zope,Plane,Django 等等)
數據庫方面最容易對付,你只要確保每張表都用UTF-8 編碼就能夠了。
數據庫適配器可能有點麻煩,有些適配器支持Unicode 有些不支持,好比說MySQLdb,它並非默認就支持Unicode 模式,你必須在connect()方法裏面用一個特殊的關鍵字use_unicode來確保你獲得的查詢結果是Unicode 字符串.
mod_python 裏面開啓對Unicode 的支持至關簡單, 只要在request 對象裏面把text-encoding 一項設成"utf-8"就好了,剩下的mod_python 都會替你完成,Zope 等其餘複雜的系統可能須要更多的工做來支持Unicode.
失誤 #1: 你必須在一個極有限的時間內寫出一個大型的應用,並且須要其餘語言的支持,可是產品經理並無明肯定義這一點。你並無kao慮Unicode 的兼容,直到項目快要結束... ,這時候再添加Unicode 的支持幾乎不太可能,不是嗎?
結果 #1: 沒能預測到最終用戶對其餘語言界面的需求,在集成他們用的面向其餘語種的應用時又沒有使用Unicode 支持.更新整個系統既讓讓人以爲枯燥和更是浪費時間。
失誤 #2:在源碼中處處使用string 模塊或者str()和chr()函數.
結果 #2:經過全局的查找替換把str()和chr()替換成unicode()和unichr(),可是這樣一來極可能就不能再用pickle 模塊,要用只能把全部要pickle 處理的數據存成二進制形式,這樣一來就必須修改數據庫的結構,而修改數據庫結構就意味着所有推倒重來.
失誤 #3: 不能肯定全部的輔助系統都徹底地支持Unicode.
結果 #3: 不得不去爲那些系統打補丁,而其中有些系統可能你根本就沒有源碼.修復對Unicode 支持的bug 可能會下降代碼的可靠性,並且很是有可能引入新的bug.
總結: 使應用程序徹底支持Unicode,兼容其餘的語言自己就是一個工程.
它須要詳細的kao慮、計劃.全部涉及到的軟件、系統都須要檢查,包括Python 的標準庫和其餘將要用到的第三方擴展模塊.你甚至有可能須要組建一個經驗豐富的團隊來專門負責國際化(I18N)問ti。
內建的unicode()函數
Unicode 的工廠方法,同Unicode 字符串操做符(u / U)的工做方式很相似,它接受一個string 作參數,返回一個Unicode 字符串.
內建的decode()/encode()方法
decode()和encode()內建函數接受一個字符串作參數返回該字符串對應的解碼後/編碼後的字符串.decode()和encode()均可以應用於常規字符串和Unicode 字符串.decode()方法是在Python2.2 之後加入的.
Unicode 類型
Unicode 字符串對象是basestring 的子類、用Unicode()工廠方法或直接在字符串前面加一個u 或者U 來建立實例.支持Unicode 原始字符串,只要在你的字符串前面加一個ur 或者UR就能夠了.
Unicode 序數
標準內建函數ord()工做方式相同,最近已經升級到能夠支持Unicode 對象了。內建的unichr()函數返回一個對應的Unicode 字符(須要一個32 位的值);不然就產生一個ValueError異常.
強制類型轉換
混合類型字符串操做須要把普通字符串轉換成Unicode 對象.
異常
UnicodeError 異常是在exceptions 模塊中定義的,ValueError 的子類.全部關於Unicode編解碼的異常都要繼承自UnicodeError.詳見encode()函數.
標準編碼
表6.9 簡潔地列出了Python 中經常使用的編碼方式.更詳細、徹底的列表見Python 的文檔,下面是它的連接:
http://docs.python.org/lib/standard-encodings.html
RE 引擎對Unicode 的支持
正則表達式引擎須要Unicode 支持.詳見6.9 節的re 模塊.
表6.9 經常使用Unicode 編輯碼
編碼 描述
utf-8 變量長度爲8 的編碼(默認編碼)
utf-16 變量長度爲16 的編碼(大/小端)
utf-16-le 小端UTF-16 編碼
utf-16-be 大端UTF-16 編碼
ascii 7-bit 7 位ASCII 碼錶
iso-8859-1 ISO 8859-1 (Latin-1) 碼錶
unicode-escape (定義見Python Unicode 構造函數)
raw-unicode-escape (定義見Python Unicode 構造函數)
native Python 用的內部格式
字符串格式化操做符
對於Python 的格式化字符串的操做符,%s 把Python 字符串中的Unicode 對象執行了str(u)操做,因此,輸出的應該是u.encode(默認編碼).若是格式化字符串是Unicode 對象,全部的參數都將首先強制轉換成Unicode 而後根據對應的格式串一塊兒進行格式轉換.數字首先被轉換成普通字符串, 而後在轉換成Unicode.Python 字符串經過默認編碼格式轉化成Unicode.Unicode 對象不變,全部其餘格式字符串都須要像上面這樣轉化,下面是例子:
u"%s %s" % (u"abc", "abc") u"abc abc"
6.9 相關模塊
表6.10 列出了Python 標準庫裏面與字符串有關的主要模塊.
核心模塊: re
正則表達式(RE)提供了高級的字符串模式匹配方法.經過描述這些模式的語法,你能夠像使用「過濾器」同樣高效地查找傳進來的文本。這些過濾器容許你基於自定義的模式字符串抽取匹配模式、執行查找-替換或分割字符串.
re全面採用了Perl 正則表達式語法,使得Python 在對正則表達式的支持方面前進了一大步. Python1.6 裏面重寫了正則表達式引擎(SRE),增長了對Unicode 字符串的支持並對性能進行了重大的升級.SRE 引擎取代了原有正則表達式的模塊下的PCRE 引擎.
該模塊中包含的關鍵函數有:
compile() - 將一個RE 表達式編譯成一個可重用的RE 對象;
match() - 試圖從字符串的開始匹配一個模式;
search() - 找出字符串中全部匹配的項;
sub() - 進行查找替換操做。其中的一些函數返回匹配到的對象,你能夠經過組匹配來訪問(若是找到的話)。
15 章的整章內容都是講述正則表達式。
一些引號分隔的字符
你能夠把字符串當作是Python 的一種數據類型,在Python 單引號或者雙引號之間的字符數組或者是連續的字符集合.字符串的實際內容是這些單引號(')或者雙引號(")之間的字符,不包括引號自己.
能夠用兩種引號來建立字符串是頗有益處的,由於是當你的字符串中包含單引號時,若是用單引號建立字符串,那麼字符串中的雙引號就不須要轉義。反之亦然.
不可分字符類型
字符串是惟一的字面上的字符序列類型.不過,字符自己並非一種類型,因此,字符串是字符存儲操做的最基本單位.字符應該視爲長度爲1 的字符串.
字符串格式化操做符 ( % )提供相似於printf()那樣的功能.
字符串格式化操做符(見6.4.1 節)提供了一種基於多種輸入類型的建立自定義字符串的靈活方式.它也提供了相似於C/C++世界裏的格式化操做的接口.
三引號
在三引號字符串中能夠包含諸如換行回車或者tab 鍵這樣的特殊字符.三引號字符串是用兩邊各三個單引號(''')或者兩邊各三個雙引號(""")來定義的.
原始字符串對每一個特殊字符串都使用它的原意
原始字符串並不經過反斜線轉義特殊字符的特性.這個特性使得原始字符串很是適用於那些須要字符串原意的場合,好比在定義一個正則表達式時.
Python 字符串不是經過NUL 或者'\0'來結束的
C 編程的一個主要問ti是你訪問了一個字符串後面的本不屬於你的空間,這種狀況發生在你沒有在字符串末尾添加終結符,NUL 或者'\0'(ASCII 值爲0)的時候.Python 不只爲你自動管理內存,並且也把C 的這個負擔或者說是小麻煩去掉了.Python 中的字符串不是以NUL 結束的,因此你不須要爲是否已經添加終結符擔憂.字符串中只包含你所定義的東西,沒有別的.
像字符串類型同樣,列表類型也是序列式的數據類型,能夠經過下標或者切片操做來訪問某一個或者某一塊連續的元素.然而,相同的方面也就這些,字符串只能由字符組成,並且是不可變的(不能單獨改變它的某個值),而列表則是能保留任意數目的Python 對象的靈活的容器。
列表不只能夠包含Python 的標準類型,並且能夠用用戶定義的對象做爲本身的元素.列表能夠包含不一樣類型的對象,並且要比C 或者Python 本身的數組類型(包含在array 擴展包中)都要靈活。列表能夠執行pop,empt,sort,reverse 等操做.列表也能夠添加或者減小元素.還能夠跟其餘的列表結合或者把一個列表分紅幾個.能夠對單獨一個元素或者多個元素執行insert,update,或者remove 操做.
元組類型在不少操做上都跟列表同樣,許多用在列表上的例子在元組上照樣能跑,它們的主要不一樣在於元組是不可變的,或者說是隻讀的,因此那些用於更新列表的操做,好比用切片操做來更新一部分元素的操做,就不能用於元組類型.
如何建立列表類型數據並給它賦值
建立一個列表就像給一個變量賦值同樣的簡單.你手工寫一個列表(空的或者有值的都行)而後賦給一個變量,列表是由方括號([])來定義的,固然,你也能夠用工廠方法來建立它.
>>> aList = [123, 'abc', 4.56, ['inner', 'list'], 7-9j] >>> anotherList = [None, 'something to see here'] >>> print aList [123, 'abc', 4.56, ['inner', 'list'], (7-9j)] >>> print anotherList [None, 'something to see here'] >>> aListThatStartedEmpty = [] >>> print aListThatStartedEmpty [] >>> list('foo') ['f', 'o', 'o']
如何訪問列表中的值
列表的切片操做就像字符串中同樣;切片操做符([])和索引值或索引值範圍一塊兒使用
如何更新列表
你能夠經過在等號的左邊指定一個索引或者索引範圍的方式來更新一個或幾個元素,你也能夠用append()方法來追加元素到列表中去.
>>> aList [123, 'abc', 4.56, ['inner', 'list'], (7-9j)] >>> aList[2] = 'float replacer' >>> aList [123, 'abc', 'float replacer', ['inner', 'list'], (7-9j)] >>> anotherList.append("hi, i'm new here") >>> print anotherList [None, 'something to see here', "hi, i'm new here"] >>> aListThatStartedEmpty.append('not empty anymore') >>> print aListThatStartedEmpty ['not empty anymore']
如何刪除列表中的元素或者列表(自己)
要刪除列表中的元素,若是你知道要刪除元素的素引能夠用del 語句,不然能夠用remove()方法.
>>> aList [123, 'abc', 'float replacer', ['inner', 'list'], (7-9j)] >>> del aList[1] >>> aList [123, 'float replacer', ['inner', 'list'], (7-9j)] >>> aList.remove(123) >>> aList ['float replacer', ['inner', 'list'], (7-9j)]
你還能夠經過pop()方法來刪除並從列表中返回一個特定對象.
通常來講,程序員不須要去刪除一個列表對象。列表對象出了做用域(好比程序結束,函數調用完成等等)後它會自動被析構,可是若是你想明確的刪除一整個列表,你能夠用del 語句:
del aList
在第4 章裏,咱們介紹了一些適用於包括標準類型在內的大部分對象的操做符,如今咱們來看一下這些操做符如何做用在列表上:
>>> list1 = ['abc', 123] >>> list2 = ['xyz', 789] >>> list3 = ['abc', 123] >>> 1ist1 < list2 True >>> list2 < list3 False >>> list2 > list3 and list1 == list3 True
比較運算符用在列表上時就不是那麼簡單了。比較列表時也是用的內建的cmp()函數,基本的比較邏輯是這樣的:兩個列表的元素分別比較,直到有一方的元素勝出,
好比咱們上面的例子,'abc'和'xyz'的比較直接決定了比較結果,在'abc'<'xyz'時,list1<list2,list2>=list3,元組類型在進行比較操做時跟列表遵循相同的邏輯.
切片([] 和[:])
列表的切片操做跟字符串的切片操做很像,不過列表的切片操做返回的是一個對象或者是幾個對象的集合,而不是像字符串那樣,返回一個字符或者一個子串.咱們定義如下幾個列表用來作例子:
>>> num_list = [43, -1.23, -2, 6.19e5] >>> str_list = ['jack', 'jumped', 'over', 'candlestick'] >>> mixup_list = [4.0, [1, 'x'], 'beef', -1.9+6j]
列表的切片操做也聽從正負索引規則,也有開始索引值,結束索引值,若是這兩個值爲空,默認也會分別指到序列的開始和結束位置.
>>> num_list[1] -1.23 >>> num_list[1:] [-1.23, -2, 619000.0] >>> num_list[2:-1] [-2] >>> >>> str_list[2] 'over' >>> str_list[:2] ['jack', 'jumped'] >>> mixup_list [4.0, [1, 'x'], 'beef', (-1.9+6j)] >>> mixup_list[1] [1, 'x']
跟字符串類型只能用字符爲元素不一樣,列表類型的元素能夠是另外一個序列類型,意味着在列表的元素上也可使用全部的序列操做符或者在其之上執行序列類型內建的各類操做.在下面的例子中,咱們將會展現,不只能夠在一個切片操做的結果之上再進行切片,並且還能夠改變這個切片的結果,即便新對象的類型跟原對象不一樣也能夠.這跟多維數組有一些相似.
>>> mixup_list[1][1] 'x' >>> mixup_list[1][1] = -64.875 >>> mixup_list [4.0, [1, -64.875], 'beef', (-1.9+6j)] 這時用num_list 來作的另外一個例子: >>> num_list [43, -1.23, -2, 6.19e5] >>> num_list[2:4] = [16.0, -49] >>> num_list [43, -1.23, 16.0, -49] >>> num_list[0] = [65535L, 2e30, 76.45-1.3j] >>> num_list [[65535L, 2e+30, (76.45-1.3j)], -1.23, 16.0, -49]
注意在最後一個例子中,是如何把列表的單一元素替換成一個列表.在列表中進行諸如remove,add,和replace 的操做是多麼的自由了吧!還有一點要注意,若是你想以子列表的形式獲得一個列表中的一個切片,那須要確保在賦值時等號的左邊也是一個列表而不是一個列表的元素.
成員關係操做( in ,not in)
列表中(一樣適用於元組),咱們能夠檢查一個對象是不是一個列表(或者元組)的成員.
>>> mixup_list [4.0, [1, 'x'], 'beef', (-1.9+6j)] >>> 'beef' in mixup_list True >>> 'x' in mixup_list False >>> 'x' in mixup_list[1] True >>> num_list [[65535L, 2e+030, (76.45-1.3j)], -1.23, 16.0, -49] >>> -49 in num_list True >>> 34 in num_list False >>> [65535L, 2e+030, (76.45-1.3j)] in num_list True
鏈接接操做符( + )
鏈接操做符容許把多個列表對象合併在一塊兒.注意,列表類型的鏈接操做也只能在同類型之間進行,換句話說,你不能把兩個不一樣類型的對象(好比:[1,2]+'a')鏈接在一塊兒,即使他們都是序列類型也不行.
>>> num_list = [43, -1.23, -2, 6.19e5] >>> str_list = ['jack', 'jumped', 'over', 'candlestick'] >>> mixup_list = [4.0, [1, 'x'], 'beef', -1.9+6j] >>> num_list + mixup_list [43, -1.23, -2, 619000.0, 4.0, [1, 'x'], 'beef', (-1.9+6j)] >>> str_list + num_list ['jack', 'jumped', 'over', 'candlestick', 43, -1.23, -2, 619000.0]
用extend()方法來代替鏈接操做符把一個列表的內容添加到另外一箇中去.使用extend()方法比鏈接操做的一個優勢是它其實是把新列表添加到了原有的列表裏面,而不是像鏈接操做那樣新建一個列表。
必須指出,鏈接操做符並不能實現向列表中添加新元素的操做.在接下來的例子中,咱們展現了一個試圖用鏈接操做向列表中添加新元素報錯的例子.
>>> num_list + 'new item' Traceback (innermost last): File "<stdin>", line 1, in ? TypeError: illegal argument type for built-in operation
錯誤是由於咱們在鏈接操做符的左右兩邊使用了不一樣類型的值.顯然,咱們的初衷是把一個字符串做爲一個新元素添加到列表中去,不過方法不正確.咱們有一個正確的方法:
使用內建函數append() (咱們會在6.13 節裏面正是地介紹append()和其餘內建函數)
>>> num_list.append('new item')
重複操做符( * )
重複操做符可能更多的應用在字符串類型中,不過,列表和元組跟字符串同屬序列類型,因此須要的時候也可使用這一操做.
>>> num_list * 2 [43, -1.23, -2, 619000.0, 43, -1.23, -2, 619000.0] >>> >>> num_list * 3 [43, -1.23, -2, 619000.0, 43, -1.23, -2, 619000.0, 43, -1.23, -2, 619000.0]
Python2.0 起,也開始支持複合賦值運算:
>>> hr = '-' >>> hr *= 30 >>> hr '------------------------------'
其實Python 中沒有專門用於列表類型的操做符.列表可使用大部分的對象和序列類型的操做符.此外,列表類型有屬於本身的方法.列表纔有的構建--列表解析.這種方法是結合了列表的方括弧和for 循環,在邏輯上描述要建立的列表的內容.咱們在第八章討論列表解析,這裏僅僅向本章其餘地方所作的那樣,展現一個簡單的例子:
>>> [ i * 2 for i in [8, -2, 5] ] [16, -4, 10] >>> [ i for i in range(8) if i % 2 == 0 ] [0, 2, 4, 6]
cmp()
咱們還不知道cmp()函數是如何跟其餘的好比列表和元組類型合做的,這些類型不只含有數字和字符串,並且還有列表,元組,字典之類的其餘對象,甚至能夠是用戶自定義的對象.這種狀況下cmp()函數是如何工做的呢?
>>> list1, list2 = [123, 'xyz'], [456, 'abc'] >>> cmp(list1, list2) -1 >>> cmp(list2, list1) 1 >>> list3 = list2 + [789] >>> list3 [456, 'abc', 789] >>> cmp(list2, list3) -1
對於序列類型,比較操做稍微有點複雜了,好比當兩個對象沒有關係時或者兩種類型根本就沒有用於比較的函數時,這時Python 只能根據"邏輯"來作出結論.
除了這種極端的狀況以外,安全又健全的比較方法是:若是有不相等的狀況出現,比較操做就結束.這種算法是如何工做的呢?
列表的元素是能夠無限迭代的.若是它的元素都是相同類型,則用標準的比較方法來做比較.不然,若是要比較的元素類型不一致,那麼要獲得一個準確的或者說絕對的比較結果就有些冒險.
當咱們較list1 和list2 時,list1 和list2 進行逐項比較.第一個比較操做發生在兩個列表的第一個元素之間,好比說,123 跟456 比較,由於123<456,因此list1 被認爲小於list2.
若是比較的值相等,那麼兩個序列的下一個值繼續比較,直到不相等的狀況出現,或者到達較短的一個序列的末尾,在這種狀況下,長的序列被認爲是"較大"的.這就是爲何上面的list2<list3 的緣由.元組類型比較也是用這種算法.最後咱們以這種算法的關鍵點做爲本節的結束:
1. 對兩個列表的元素進行比較.
2. 若是比較的元素是同類型的,則比較其值,返回結果.
3. 若是兩個元素不是同一種類型,則檢查它們是不是數字.
a. 若是是數字,執行必要的數字強制類型轉換,而後比較.
b. 若是有一方的元素是數字,則另外一方的元素"大"(數字是"最小的")
c. 不然,經過類型名字的字母順序進行比較.
4. 若是有一個列表首先到達末尾,則另外一個長一點的列表"大".
5. 若是咱們用盡了兩個列表的元素並且全部元素都是相等的,那麼結果就是個平局,就是說返回一個0.
len()
對列表或者元組來講,返回列表或者元組的元素個數,容器裏面的每一個對象被做爲一個項來處理:
>>> len(num_list) 4 >>> len(num_list*2) 8
max()和min()
max()和min()函數對列表和元組來講,它們被定義了不少的用處.好比對只包含數字和字符串對象的列表,max()和min()函數就很是有用,混合對象的結構越複雜返回的結構準確性就越差:
>>> max(str_list) 'park' >>> max(num_list) [65535L, 2e+30, (76.45-1.3j)] >>> min(str_list) 'candlestick' >>> min(num_list) -49
sorted() and reversed()
>>> s = ['They', 'stamp', 'them', 'when', "they're", 'small'] >>> for t in reversed(s): ... print t, ... small they're when them stamp They >>> sorted(s) ['They', 'small', 'stamp', 'them', "they're", 'when']
注意字符串排序使用的是字典序,而不是字母序(字母'T'的ASCII 碼值要比字母'a'的還要靠前)
enumerate() and zip()
>>> albums = ['tales', 'robot', 'pyramid'] >>> for i, album in enumerate(albums): ... print i, album ... 0 tales 1 robot 2 pyramid >>> fn = ['ian', 'stuart', 'david'] >>> ln = ['bairnson', 'elliott', 'paton'] >>> for i, j in zip(fn, ln): ... print ('%s %s' % (i,j)).title() ... Ian Bairnson Stuart Elliott David Paton
sum()
>>> a = [6, 4, 5] >>> reduce(operator.add, a) 15 >>> sum(a) 15 >>> sum(a, 5) 20 >>> a = [6., 4., 5.] >>> sum(a) 15.0
list() and tuple()
list()函數和tuple()函數接受可迭代對象(好比另外一個序列)做爲參數,並經過淺拷貝數據來建立一個新的列表或者元組.雖然字符串也是序列類型的,可是它們並非常常用於list()和tuple(). 更多的狀況下,它們用於在兩種類型之間進行轉換,好比你須要把一個已有的元組轉成列表類型的(而後你就能夠修改它的元素了),或者相反.
>>> aList = ['tao', 93, 99, 'time'] >>> aTuple = tuple(aList) >>> aList, aTuple (['tao', 93, 99, 'time'], ('tao', 93, 99, 'time')) >>> aList == aTuple False >>> anotherList = list(aTuple) >>> aList == anotherList True >>> aList is anotherList False >>> [id(x) for x in aList, aTuple, anotherList] [10903800, 11794448, 11721544]
正如咱們在本章的開頭所討論的,不管list()仍是tuple()都不可能作徹底的轉換(見6.1.2 節).也就是說,傳給tuple()的一個列表對象不可能變成一個元組,而你傳給list()的對象也不可能真正的變成一個列表.雖然先後兩個對象(原來的和新的對象)有着相同的數據集合(因此相等 == ),可是變量指向的卻不是同一個對象了(因此執行 is 操做會返回false).還要注意,即便它們的全部的值都相同,一個列表也不可能"等於"一個元組.
若是你不考慮range()函數的話,Python 中沒有特定用於列表的內建函數.range()函數接受一個數值做爲輸入,輸出一個符合標準的列表.第8 章裏面詳細討論了range()函數.列表類型對象可使用大多數的對象和序列的內建函數,而且,列表對象有屬於它們本身的方法.
Python 中的列表類型有本身的方法.咱們會在第13 章面向對象編程裏面正式而詳細的介紹方法這一律念,如今你只須要把方法視爲特定對象的函數或者過程就好.本節討論的方法就像內建的函數同樣,除了它們只對列表類型進行操做以外.由於這些函數涉及到對列表更改(或者說更新),因此它們都不適應於元組.
列表的方法也是這樣:list.method().咱們用點號來訪問一個對象的屬性,而後用函數操做符( () )來調用這個方法.
能夠在一個列表對象上應用dir()方法來獲得它全部的方法和屬性:
>>> dir(list) # or dir([]) ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__str__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
表6.11 列出了目前列表類型支持的全部方法,稍後咱們給出使用這些方法的例子.
>>> music_media = [45] >>> music_media [45] >>> music_media.insert(0, 'compact disc') >>> music_media ['compact disc', 45] >>> music_media.append('long playing record') >>> music_media ['compact disc', 45, 'long playing record'] >>> music_media.insert(2, '8-track tape') >>> music_media ['compact disc', 45, '8-track tape', 'long playing record']
在這個例子中,向列表插入元素,或在尾部追加新的元素後,都會去檢查這個列表.如今確認一下一個值是否在咱們的列表中,並看看如何找出元素在列表中的索引值.咱們用in 操做符和index()方法實現這兩個需求.
>>> 'cassette' in music_media False >>> 'compact disc' in music_media True >>> music_media.index(45) 1 >>> music_media.index('8-track tape') 2 >>> music_media.index('cassette') Traceback (innermost last): File "<interactive input>", line 0, in ? ValueError: list.index(x): x not in list
看起來用index()來檢查一個元素是否存在於一個list中並非個好主意,由於咱們出錯了.應該先用in 成員關係操做符(或者是not in)檢查一下,而後在用index()找到這個元素的位置:
for eachMediaType in (45, '8-track tape', 'cassette'): if eachMediaType in music_media: print music_media.index(eachMediaType)
稍後咱們將會發現該如何處理這種錯誤,而不是這樣的一出錯,程序就崩潰了。
接下來測試sort()和reverse()方法,它們會把列表中的元素排序,而後翻轉.
>>> music_media ['compact disc', 45, '8-track tape', 'long playing record'] >>> music_media.sort() >>> music_media [45, '8-track tape', 'compact disc', 'long playing record'] >>> music_media.reverse() >>> music_media ['long playing record', 'compact disc', '8-track tape', 45]
核心筆記:那些能夠改變對象值的可變對象的方法是沒有返回值的
Python 初學者常常會陷入一個誤區:調用一個方法就返回一個值.最明顯的例子就是sort():
>>> music_media.sort()# 沒有輸出? >>>
在使用可變對象的方法如sort(),extend()和reverse()的時候要注意,這些操做會在列表中原地執行操做,也就是說現有的列表內容會被改變,可是沒有返回值!是的,與之相反,字符串方法確實有返回值:
>>> 'leanna, silly girl!'.upper() 'LEANNA, SILLY GIRL!'
溫習一下,字符串是不可變的 -- 不可變對象的方法是不能改變它們的值的,因此它們必須返回一個新的對象.若是你確實須要返回一個對象,那麼咱們建議你看一下Python2.4 之後加入的reversed()和sorted()內建函數.
它們像列表的方法同樣工做,不一樣的是它們能夠用作表達式,由於它們返回一個對象.同時原來的那個列表仍是那個列表,沒有改變,而你獲得的是一個新的對象.
回到sort()方法,它默認的排序算法是歸併排序的衍生算法,時間複雜度是O(lg(n!)).能夠經過源碼查看它們的詳情 --Objects/listobject.c,還有算法描述: Objects/listsort.txt.
extend()方法接受一個列表的內容而後把它的全部元素追加到另外一個列表中去:
>>> new_media = ['24/96 digital audio disc', 'DVD Audio disc', 'Super Audio CD'] >>> music_media.extend(new_media) >>> music_media ['long playing record', 'compact disc', '8-track tape', 45, '24/96 digital audio disc', 'DVD Audio disc', 'Super Audio CD']
extend()方法的參數支持任何可迭代對象,經過可迭代對象,你能作更多有趣的事情,好比:
>>> motd = [] >>> motd.append('MSG OF THE DAY') >>> f = open('/etc/motd', 'r') >>> motd.extend(f) >>> f.close() >>> motd ['MSG OF THE DAY', 'Welcome to Darwin!\n']
1.5.2 中加入的pop()方法會從列表中把最後的或指定的元素返回調用者.咱們會在6.15.1節和練習中看到pop()方法,
列表有容器和可變的特性,這使得它很是靈活,用它來構建其餘的數據結構不是件難事.咱們立刻能想到的是堆棧和隊列.
堆棧
堆棧是一個後進先出(LIFO)的數據結構.在棧上"push"元素是個經常使用術語,意思是把一個對象添加到堆棧中。反之,要刪除一個元素,你能夠把它"pop"出堆棧。
例6.3展現了一個菜單驅動的程序,它實現了一個簡單的、用於存儲字符串的堆棧.
stack = [] def pushit(): stack.append(input("enter new string: ").strip()) def popit(): if len(stack) == 0: print ('cannot pop from an empty stack!') else: print ('remove[',stack.pop(), ']') def viewstack(): print (stack) CMDS = {'u':pushit, 'o':popit, 'v':viewstack} def showmenu(): pr="""p(U)sh p(O)p (V)iew (Q)uit Enter choice:""" while True: while True: try: choice = input(pr).strip()[0].lower() except(EOFError, KeyboardInterrupt, IndexError): choice='q' print ('your choice',choice) if choice not in 'uovq': print ('invalid option, try again') else: break if choice == 'q': break CMDS[choice]() if __name__ == "__main__": showmenu()
隊列
隊列是一種先進先出(FIFO)的數據類型,.新的元素經過"入隊"的方式添加進隊列的末尾,"出隊"就是從隊列的頭部刪除.下面的例子裏面展現了這種操做,咱們把上面的堆棧的例子進行如下改造就能夠用列表實現了一個簡單的隊列.
print ('remove[',stack.pop(0), ']') #其他都同樣,只不過出來的時候出來第一個
元組跟列表很是相近的另外一種容器類型.元組和列表看起來不一樣的一點是元組用的是圓括號而列表用的是方括號。而功能上,元組和列表相比有一個很重要的區別,元組是一種不可變類型.正由於這個緣由,元組能作一些列表不能作的事情... 用作一個字典的key.另外當處理一組對象時,這個組默認是元組類型.
因爲元組類型跟列表類型有着很是多的共同之處,爲了不太多重複信息,咱們會講解元組和列表在應用於每一組操做符和內建函數上時的區別,而後討論一下元組的不變性以及其餘獨特的特性.
如何建立一個元組並給它賦值
建立一個元組並給他賦值實際上跟建立一個列表並給它賦值徹底同樣,除了一點,只有一個元素的元組須要在元組分割符裏面加一個逗號(,)用以防止跟普通的分組操做符混淆.不要忘了它是一個工廠方法!
>>> aTuple = (123, 'abc', 4.56, ['inner', 'tuple'], 7-9j) >>> anotherTuple = (None, 'something to see here') >>> print aTuple (123, 'abc', 4.56, ['inner', 'tuple'], (7-9j)) >>> print anotherTuple (None, 'something to see here') >>> emptiestPossibleTuple = (None,) >>> print emptiestPossibleTuple (None,) >>> tuple('bar') ('b', 'a', 'r')
如何訪問元組中的值
元組的切片操做跟列表同樣
如何更新元組
跟數字和字符串同樣,元組也是不可變類型,就是說你不能更新或者改變元組的元素,在6.2和6.3.2 節裏面,咱們是經過現有字符串的片斷再構造一個新字符串的方式解決的,對元組一樣須要這樣.
>>> aTuple = aTuple[0], aTuple[1], aTuple[-1] >>> aTuple (123, 'abc', (7-9j)) >>> tup1 = (12, 34.56) >>> tup2 = ('abc', 'xyz') >>> tup3 = tup1 + tup2 >>> tup3 (12, 34.56, 'abc', 'xyz')
如何移除一個元組的元素以及元組自己
刪除一個單獨的元組元素是不可能的,固然,把不須要的元素丟棄後, 從新組成一個元組是ok的.
要顯示地刪除一整個元組,只要用del 語句減小對象引用計數.當這個引用計數達到0 的時候,該對象就會被析構.記住,大多數時候,咱們不須要顯式的用del 刪除一個對象,一出它的做用域它就會被析構,Python 編程裏面用到顯式刪除元組的狀況很是之少.
del aTuple
元組的對象和序列類型操做符還有內建函數跟列表的徹底同樣.你仍然能夠對元組進行切片操做,合併操做,以及屢次拷貝一個元組,還能夠檢查一個對象是否屬於一個元組,進行元組之間的比較等.
建立,重複,鏈接操做
>>> t = (['xyz', 123], 23, -103.4) >>> t (['xyz', 123], 23, -103.4) >>> t * 2 (['xyz', 123], 23, -103.4, ['xyz', 123], 23, -103.4) >>> t = t + ('free', 'easy') >>> t (['xyz', 123], 23, -103.4, 'free', 'easy')
成員關係操做,切片操做
>>> 23 in t True >>> 123 in t False >>> t[0][1] 123 >>> t[1:] (23, -103.4, 'free', 'easy')
內建函數
>>> str(t) (['xyz', 123], 23, -103.4, 'free', 'easy') >>> len(t) 5 >>> max(t) 'free' >>> min(t) -103.4 >>> cmp(t, (['xyz', 123], 23, -103.4, 'free', 'easy')) 0 >>> list(t) [['xyz', 123], 23, -103.4, 'free', 'easy']
操做符
>>> (4, 2) < (3, 5) False >>> (2, 4) < (3, -1) True >>> (2, 4) == (3, -1) False >>> (2, 4) == (2, 4) True
像列表同樣 元組也沒有它本身專用的運算符和內建函數.上一節中描述的列表方法都跟列表對象的可變性有關,好比說排序,替換,添加等等,由於元組是不可變的,因此這些操做對元組來講就是多餘的,這些方法沒有被實現.
在好多地方使用到了"不可變性"這個單詞,除了這個詞的計算機學科定義和實現,從應用的角度來考慮,這個詞的底線是什麼?一個數據類型成爲不可變的到底意味着什麼?
在三個標準不可變類型裏面--數字,字符串和元組字符串--元組是受到影響最大的,一個數據類型是不可變的,簡單來說,就意味着一旦一個對象被定義了,它的值就不能再被更新,除非從新建立一個新的對象.對數字和字符串的影響不是很大,由於它們是標量類型,當它們表明的值改變時,這種結果是有意義的,是按照你所想要的方式進行訪問的,而對於元組,事情就不是這樣了。
由於元組是容器對象,不少時候你想改變的只是這個容器中的一個或者多個元素,不幸的是這是不可能的,切片操做符不能用做左值進行賦值。這和字符串沒什麼不一樣,切片操做只能用於只讀的操做。
不可變並非壞事,好比咱們把數據傳給一個不瞭解的API 時,能夠確保咱們的數據不會被修改。一樣地,若是咱們操做從一個函數返回的元組,能夠經過內建list()函數把它轉換成一個列表.
6.18.2 元組也不是那麼「不可變」
雖然元組是被定義成不可變的,但這並不影響它的靈活性。元組並不像咱們想的那麼不可變,其實元組幾個特定的行爲讓它看起來並不像咱們先前聲稱的那麼不可變.
好比說,既然咱們能夠把字符串組合在一塊兒造成一個大字符串。那麼把元組組合在一塊兒造成一個大的元組也沒什麼不對,因此,鏈接操做可用,這個操做一點都沒有改變那些小元組:
>>> s = 'first' >>> s = s + ' second' >>> s 'first second' >>> t = ('third', 'fourth') >>> 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', -4.24e93, 18+6.6j, 'xyz' ('abc', -4.24e+093, (18+6.6j), 'xyz') >>> x, y = 1, 2 >>> x, y (1, 2)
全部函數返回的多對象(不包括有符號封裝的)都是元組類型。注意,有符號封裝的多對象集合實際上是返回的一個單一的容器對象,好比:
def foo1(): return obj1, obj2, obj3 def foo2(): return [obj1, obj2, obj3] def foo3(): return (obj1, obj2, obj3)
上面的例子中,foo1()返回3 個對象,默認的做爲一個包含3 個對象的元組類型,foo2()返回一個單一對象,一個包含3 個對象的列表,還有foo3()返回一個跟foo1()相同的對象.惟一不一樣的是這裏的元組是顯式定義的.
爲了不使人討厭的反作用,建議老是顯式的用圓括號表達式表示元組或者建立一個元組.
>>> 4, 2 < 3, 5 # int, comparison, int (4, True, 5) >>> (4, 2) < (3, 5) # tuple comparison False
在第一個例子中小於號的優先級高於逗號,2<3 的結果成了元組變量的第二個元素,適當的封裝元組就會獲得但願獲得的結果.
曾經試過建立一個只有一個元素的元組?你在列表上試過,它能夠完成,可是不管你怎麼在元組上試驗,你都不能獲得想要的結果。
>>> ['abc'] ['abc'] >>> type(['abc']) # a list <type 'list'> >>> ('xyz') 'xyz' >>> type(('xyz')) # a string, not a tuple <type 'str'>
或許你忘記了圓括號被重載了,它也被用做分組操做符。由圓括號包裹的一個單一元素首先被做爲分組操做,而不是做爲元組的分界符。一個變通的方法是在第一個元素後面添一個逗號(,)來代表這是一個元組而不是在作分組操做.
>>> ('xyz',) ('xyz',)
不可變對象的值是不可改變的。這就意味着它們經過hash 算法獲得的值老是一個值。這是做爲字典鍵值的一個必備條件。在下一章節裏面咱們會討論到,鍵值必須是可哈希的對象,元組變量符合這個標準,而列表變量就不行。
核心筆記:列表 VS 元組
"爲何咱們要區分元組和列表變量?",一個緣由是在有些狀況下,使用其中的一種類型要優於使用另外一種類型。
最好使用不可變類型變量的一個狀況是:你在維護一些敏感的數據,而且須要把這些數據傳遞給一個並不瞭解的函數(或許是一個根本不是你寫的API),做爲一個只負責一個軟件某一部分的工程師,若是你確信你的數據不會被調用的函數篡改,你會以爲安全了許多。
一個須要可變類型參數的例子是:你在管理動態數據集合時。你須要先把它們建立出來,逐漸地或者不按期的添加它們,或者有時還要移除一些單個的元素。這是一個必須使用可變類型對象的典型例子。幸運的是,經過內建的list()和tuple()轉換函數,你能夠很是輕鬆的在二者之間進行轉換.
list()和tuple()函數容許你用一個列表來建立一個元組,反之亦然.若是你有一個元組變量,但你須要一個列表變量由於你要更新一下它的對象,這時list()函數就是你最好的幫手.若是你有一個列表變量,而且想把它傳遞給一個函數,或許一個API,而你又不想讓任何人弄亂你的數據,這時tuple()函數就很是有用。
表6.12 列出了與序列類型相關的關鍵模塊,這個列表包含了前面咱們間接提到的數組模塊,它就像列表類型,不過它要求全部的元素都是同一類型。
Table 6.12 與序列類型相關的模塊
operator 模塊除了提供與數字操做符相同的功能外,還提供了與序列類型操做符相同的功能.types 模塊是表明python 支持的所有類型的type 對象的引用。最後,UserList 模塊包含了list 對象的徹底的類實現。由於Python 類型不能做爲子類,因此這個模塊容許用戶得到相似list 的類,也能夠派生出新的類或功能。若是你熟悉面向對象編程的話,咱們強烈推薦你閱讀第13 章
淺拷貝和深拷貝
咱們講過對象賦值其實是簡單的對象引用。當你建立一個對象,而後把它賦給另外一個變量的時候,Python 並無拷貝這個對象,而是拷貝了這個對象的引用。
假設你想建立一對小夫妻的通用檔an,名爲person.而後你分別爲他倆拷貝一份。
咱們展現了兩種拷貝對象的方式,一種使用了切片操做,另外一種用了工廠方法,爲了區分出三個不一樣的對象,咱們使用id()內建函數來顯示每一個對象的標識符。(咱們還能夠用is 操做符來作相同的事情)
>>> 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]
爲他們建立了初始有$100 的我的存款賬戶。用戶名改成定製的名字。可是,當丈夫取走$50後,他的行爲影響到了他妻子的帳戶,雖然咱們進行了分開的拷貝,爲何會這樣呢?
>>> 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]])
緣由是咱們僅僅作了一個淺拷貝。對一個對象進行淺拷貝實際上是新建立了一個類型跟原對象同樣,其內容是原來對象元素的引用,換句話說,這個拷貝的對象自己是新的,可是它的內容不是 .序列類型對象的淺拷貝是默認類型拷貝,並能夠如下幾種方式實施:
(1)徹底切片操做[:]
(2)利用工廠函數,好比list(),dict()等
(3)使用copy 模塊的copy 函數.
但當妻子的名字被賦值,爲何丈夫的名字沒有受到影響?難道它們的名字如今不該該都是'jane'了嗎?這是由於在這兩個列表的兩個對象中,第一個對象是不可變的(是個字符串類型),而第二個是可變的(一個列表).正由於如此,當進行淺拷貝時,字符串被顯式的拷貝,並新建立了一個字符串對象,而列表元素只是把它的引用複製了一下,並非它的成員.因此改變名字沒有任何問ti,可是更改他們銀行帳號的任何信息都會引起問ti.如今,讓咱們分別看一下每一個列表的元素的對象ID 值,注意,銀行帳號對象是同一個對象,這也是爲何對一個對象進行修改會影響到另外一個的緣由.
BEFORE: >>> [id(x) for x in hubby] [9919616, 11826320] >>> [id(x) for x in wifey] [9919616, 11826320] AFTER: >>> [id(x) for x in hubby] [12092832, 11826320] >>> [id(x) for x in wifey] [12191712, 11826320]
假設咱們要給這對夫妻建立一個聯合帳戶,那這是一個很是棒的程序,可是,若是須要的是兩個分離帳戶,就須要做些改動了.要獲得一個徹底拷貝或者說深拷貝--建立一個新的容器對象,包含原有對象元素(引用)全新拷貝的引用--須要copy.deepcopy()函數.咱們使用深拷貝來重寫整個例子.
>>> person = ['name', ['savings', 100.00]] >>> hubby = person >>> import copy >>> wifey = copy.deepcopy(person) >>> [id(x) for x in person, hubby, wifey] [12242056, 12242056, 12224232] >>> 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', 100.0]])
這就是咱們想要的方式,做爲驗證,讓咱們確認一下全部四個對象都是不一樣的.
>>> [id(x) for x in hubby] [12191712, 11826280] >>> [id(x) for x in wifey] [12114080, 12224792]
如下有幾點關於拷貝操做的警告。第一,非容器類型(好比數字,字符串和其餘"原子"類型的對象,像代碼,類型和xrange 對象等)沒有被拷貝一說,淺拷貝是用徹底切片操做來完成的.第二,若是元組變量只包含原子類型對象,對它的深拷貝將不會進行.若是咱們把帳戶信息改爲元組類型,那麼即使按咱們的要求使用深拷貝操做也只能獲得一個淺拷貝:
>>> person = ['name', ('savings', 100.00)] >>> newPerson = copy.deepcopy(person) >>> [id(x) for x in person, newPerson] [12225352, 12226112] >>> [id(x) for x in person] [9919616, 11800088] >>> [id(x) for x in newPerson] [9919616, 11800088]
核心模塊: copy
淺拷貝和深拷貝操做均可以在copy 模塊中找到.其實copy 模塊中只有兩個函數可用:copy()進行淺拷貝操做,而deepcopy()進行深拷貝操做.
序列類型爲數據的順序存儲提供了幾種機制.字符串是最經常使用的數據載體,不管是用於給用戶顯示,存貯到硬盤,經過網絡傳輸,仍是做爲一個多源信息的容器.列表和元組提供了容器存儲能力,容許簡單的操做和訪問多個對象,不管它們是Python 的對象仍是用戶自定義的對象.單一元素或一組元素能夠經過持續有序地索引偏移進行切片操做來訪問.總之,這些數據類型爲你的Python 開發環境提供了靈活而易用的存貯工具.咱們用表6.13--序列類型的操做符,內建函數和方法的摘要列表來總結本章.
Table 6.13 序列類型操做符,內建函數和方法