本節是第五講的第四小節,上節為大家介紹了Python語言的數字類型包括(整型、布爾型、浮點型、複數型、十進位型),本節將為大家介紹項目中最常用類型字符串型,由於內容較多,分為上下兩部分講解。
字符串(Strings)
字符串是使用固定不變的str數據類型表示的,其中存放Unicode字符序列。str 數據類型可以作為函數進行調用,用於創建字符串對象——參數為空時返回一個空字符串,參數為非字符串類型時返回該參數的字符串形式,參數為字符串時返回該字符串的拷貝。str()函數也可以用作一個轉換函數,此時要求第一個參數為字符串或可以轉換為字符串的其他數據類型,其後跟隨至多兩個可選的字符串參數,其中一個用於指定要使用的編碼格式,另一個用於指定如何處理編碼錯誤。
前面我們注意到,字符串是使用引號創建的,可以使用單引號,也可以使用雙引號,但是字符串兩端必須相同。此外,我們還可以使用三引號包含的字符串——這是Python對起始端與終端都使用3個引號包含的字符串的叫法,例如:
text ="""A triple quoted string like this can include 'quotes' and
"quotes" without formality. We can also escape newlines \
so this particular string is actually only two lines long."""
a = "Single 'quotes' are fine; \"doubles\" must be escaped."
b = 'Single \'quotes\' must be escaped; "doubles" are fine.'
如果需要在通常的、引號包含的字符串中使用引號,在要使用的引號與包含字符串的引號不同時,可以直接使用該引號,而不需要進行格式化處理操作,但是如果相同,就必須對其進行轉義。
Python使用換行作為其語句終結符,但是如果在圓括號內、方括號內、花括號內或三引號包含的字符串內則是例外。在三引號包含的字符串中,可以直接使用換行, 而不需要進行格式化處理操作。
Python字符串轉義
\newline 忽略換行 \\ 反斜槓(\) \' 單引號(')
\" 雙引號(「) \a ASCII 蜂鳴(BEL) \b ASCII 退格(BS)
\f ASCII 走紙(FF) \n ASCII 換行(LF) \N{name} 給定名稱的Unicode字符
\ooo 給定八進位值的字符 \r ASCII回車符(CR) \t ASCII 制表符(TAB)
\uhhhh 給定16位十六進位值的Unicode字符 \v ASCII垂直指標(VT) \xhh 給定8位十六進位值的Unicode字符
如果需要寫一個長字符串,跨越了2行或更多行,但是不使用三引號包含的字符串,那麼有兩種解決方法:
t = "This is not the best way to join two long strings" + \
"together since it relies on ugly newline escaping"
s = ("This is the nice way to join two long strings "
"together; it relies on string literal concatenation.")
注意上面第二種情況,我們必須使用圓括號將其包含在一起,構成一個單獨的表達式——如果不使用圓括號,就只有第一個字符串對S進行賦值,第二個字符串則會導致 IndentationError 異常。Python 的"Idioms and Anti-Idioms" HOWTO文檔建議總是使用圓括號將跨越多行的任何語句進行封裝,而不使用轉義的換行符,我們努力遵照這一建議。
由於.py文件默認使用UTF-8 Unicode編碼,因此我們可以在字符串字面值中寫入任意Unicode字符,而不拘形式。我們也可以使用十六進位轉義序列(或使用Unicode名)將任意Unicode字符放置在字符串內,例如:
>>> euros = "€\N{euro sign}\u20AC\U000020AC"
>>> print(euros)
€€€€
上面的情況不能使用十六進位轉義序列,因為本身限定在兩個digits,無法超過 0xFF。要注意的是,Unicode字符名非大小寫敏感,其中的空格也是可選的。
如果需要知道字符串中某個特定字符的Unicode字元(賦予Unicode編碼中某個字符的整數值),那麼可以使用內置的ord()函數,例如:
>>> ord(euros[0])
8364
>>> hex(ord(euros[0]))
'0x20ac『
>>> s = "anarchists are " + chr(8734) + chr(0x23B7)
>>> s
'anarchists are ∞⎷'
>>> ascii(s)
"'anarchists are \\u221e\\u23b7'"
將表示有效字元的任意整數轉換為相應的Unicode字符,這需要使用內置的chr()函數。如果在IDLE中輸入本身,就輸出其字符串形式。對於字符串,這意味著字符是包含在引號中輸出的。如果只需要ASCII字符,就可以使用內置的ascii。函數,在可能的地方,該函數使用7比特表示形式返回其參數的對應ASCII表示,否則就使用最短的\xhh、\uhhhh或\Uhhhhhhhh進行轉義。
比較字符串(Comparing Strings)
字符串支持通常的比較操作符<、<=、==、!=、>與>=,這些操作符在內存中逐個字節對字符串進行比較。遺憾的是,進行比較時(比如對字符串列表進行排序),存在兩個問題,這兩個問題都影響到每種使用Unicode字符串的程序設計語言,不是Python 特有的問題。
第一個問題是,有些Unicode字符可以使用兩種或更多種字節序列表示。例如, 字符A⁰(Unicode字元0x00C5)可以3種不同的方式使用UTF-8編碼的字節表示:[0xE2, 0x84, 0xAB]、[0xC3, 0x85]與[0x41, 0xCC, 0x8A]。幸運的是,我們可以解決這一問題。如果我們導入了 unicodedata模塊,並以「NFKD」(這是使用的標準化方法,代表 Normalization Form Compatibility Decomposition")為第一個參數來調用 unicodedata.normalize(),則對包含字符A(使用任意一種有效字符序列表示)的字符串,該函數返回以UTF-8編碼字節表示的字符串總是字節序列[0x41, 0xCC, 0x8A]。
第二個問題是,有些字符的排序是特定於某種語言的。一個實例是在瑞典語中,ā 排序在z之後,而在德語中,ā的排序與其被拼寫為ae時一樣。另一個實例是,在英語中,對¢排序時,與其為o一樣,在丹麥語與挪威語中,則排序在z之後。這一類問題還有很多,由於同一個應用程式可能會由不同國家的人(因此所認為的排序順序會不同)使用,使得這一問題變得更加複雜。此外,有時候字符串是不同語言混合組成的(比如,有些是西班牙語,有些是英語),而有些字符(比如箭頭、dingbats與數學符號)並不真正具備有意義的排序位置。
作為一種策略(以便防止出錯),Python並不進行推測。在字符串比較時,Python使用的是字符串的內存字節表示,此時的排序是基於Unicode字元的,比如對英語就是按ASCII順序。對要比較的字符串進行小寫或大寫,會產生更貼近自然英語的排序。標準化一般很少需要,除非字符串來自外部源(比如文件或網絡socket),但即便是這些情況,一般也不必進行標準化,除非確實需要。
字符串分片與步距(Slicing and Striding Strings)
從要素3的講解中我們知道,序列中的單個數據項或者字符串中的單個字符,可以使用數據項存取操作符[]來提取。實際上,這一操作符功能很豐富,其用途不僅僅局限於提取一個數據項或字符,還可以提取項或字符的整個分片(子序列),在這種情況下該操作符被用作分片操作符。
我們首先從提取單個字符開始。字符串的索引位置從0開始,直至字符串長度值減去1,但是使用負索引位置也是可能的——此時的計數方式是從最後一個字符到第一個字符。給定賦值操作s = "Light ray",
負索引值出人意料地有用,尤其是-1,這個值總是代表字符串的最後一個字符。存取超過範圍的索引位置(或空字符串中的索引位置)會產生IndexError異常。
分片操作符有3種語法格式:
seq[start]
seq[start:end]
seq[start:end:step]
其中,seq可以是任意序列,比如列表、字符串或元組。start、end與step必須都是整數(或存放整數的變量)。我們使用了第一種語法格式:從序列中提取從start開始的數據項。第二種語法從start開始的數據項(包含)到end結束的數據項(不包含) 提取一個分片。稍後我們將討論第三種語法格式。
如果使用第二種語法格式(一個冒號),我們就可以忽略任意的整數索引值。如果忽略了所有起點索引值,就默認為0;如果忽略了終點索引值,就默認為len(seq),這意味著,如果忽略了兩個索引值,比如,s[:],則與s[0:len(s)]是等同的,其作用都是提取--也就是複製整個序列。
給定賦值操作s = "The waxwork man",在字符串內插入子字符串的一種方法是混合使用帶連接的分片,例如:
>>> s = s[:12] + "wo" + s[12:]
>>> s
The waxwork woman'
實際上,由於文本」wo」在原始字符串中,因此我們也可以寫成s[:12] + s[7:9] + s[12:] 達到同樣的效果。在涉及很多字符串時,使用+進行連接、使用+=進行追加等操作並不是特別高效, 如果需要連接大量的字符串,通常最好使用str.join()方法。
第三種分片語法格式(兩個冒號)與第二種類似,區別在於不是提取每一個字符,而是每隔step個字符進行提取。與第二種語法類似,也可以忽略兩個索引整數。如果忽略了起點索引值,那麼默認為0—除非給定的是負的step值,此時起點索引值默認為-1;如果忽略終點索引值,那麼默認為len(seq)——除非給定的是負的step值, 此時終點索引值默認為字符串起點前面。不過,不能忽略step,並且step不能為0。如果不需要step,那麼應該使用不包含step變量的第二種語法(一個冒號)。
給定賦值操作s = "he ate camel food",下圖展示了字符串帶步距的分片的兩個實例。
上面我們使用默認的起點索引值與終點索引值,因此,s[::-2]從該字符串的最後一個字符開始,向該字符串的起點方向,每隔1個字符提取一個字符。類似地,s[::3]從 第一個字符開始,向該字符串的終點方向,每隔2個字符提取一個字符。
將分片與步距結合使用也是可能的,如下圖所示。
更常見的情況下,步距是與字符串之外的序列類型一起使用的,但是也存在用於字符串的情況:
>>>s,s[::-1]
(』The waxwork woman', 'namow krowxaw ehT')
#step為-1意味著,每個字符都將被提取,方向為從終點到起點——因此會產生反轉的字符串。
字符串操作符與方法(String Operators and Methods)
由於字符串是固定序列,所有可用於固定序列的功能都可用於字符串,包括使用 in進行成員關係測試,使用+=進行追加操作,使用*進行複製,使用*=進行增強的賦值複製等。在這一小節中,我們將在字符串的上下文中討論所有這些操作,此外還討論了很多字符串方法。
字符串方法
s.capitalize() 返回字符串s的副本,並將首字符變為大寫,參見str.title()方法
s.center(width, char) 返回s中間部分的一個子字符串,長度為width,並使用空格或可選的char(長度為1 的字符串)進行填充。參考 str.ljust() 、str.rjust()與 str.format()
s.count(t, start, end) 返回字符串s中(或在s的start:end分片中)子字符串t出現的次數
s.encode(encoding, err) 返回一個bytes對象,該對象使用默認的編碼格式或指定的編碼格式來表示該字符串, 並根據可選的err參數處理錯誤
s.join(seq) 返回序列seq中每個項連接起來後的結果,並以s (可以為空)在每兩項之間分隔
s.startswith(x, start, end) 如果s (或s的startend分片)以字符串x開始(或以元組x中的任意字符串開始),就返回 True,否則返回 False,參考 str.endswith()
s.endswith(x, start, end) 如果s (或在s的start:end分片)以字符串x (或元組x中的任意字符串)結尾,就返回 true,否則返回 False,參考 str.startswith()
s.expandtabs(size) 返回s的一個副本,其中的制表符使用8個或指定數量的空格替換
s.find(t, start, end) 返回t在s中(或在s的start:end分片中)的最左位置,如果沒有找到,就返回-1;使用str.rfind()則可以發現相應的最右邊位置;參考str.index()
s.format(...) 返回按給定參數進行格式化後的字符串副本
s.index(t, start, end) 返回t在s中的最左邊位置(或在s的start:end分片中),如果沒有找到,就產生ValueError 異常。使用str.rindex()可以從右邊開始搜索。參見str.find()
s.isalnum() 如果s非空,並且其中的每個字符都是字母數字的,就返回True
s.isalpha() 如果s非空,並且其中的每個字符都是字母的,就返回True
s.isdecimal() 如果s非空,並且其中的每個字符都是Unicode的基數為10的數字,就返回True
s.isdigit() 如果s非空,並且每個字符都是一個ASCII數字,就返回True
s.isidentifier() 如果s非空,並且是一個有效的標識符,就返回True
s.islower() 如果s至少有一個可小寫的字符,並且其所有可小寫的字符都是小寫的,就返回True, 參見 str.isupper()
s.isnumeric() 如果s非空,並且其中的每個字符都是數值型的Unicode字符,比如數字或小數,就返回 True
s.isprintable() 如果s非空,並且其中的每個字符被認為是可列印的,包括空格,但不包括換行,就返回True
s.isspace() 如果s非空,並且其中的每個字符都是空白字符,就返回True
s.istitle() 如果s是一個非空的首字母大寫的字符串,就返回True,參見str.title()
s.isupper() 如果s至少有一個可大寫的字符,並且所有可大寫的字符都是大寫,就返回True,參見 str.islower()
s.ljust(width, char) 返回長度為width的字符串(使用空格或可選的char (長度為1的字符串)進行填充)中左對齊的字符串s的一個副本,使用str.rjust()可以右對齊,str.center()可以中間對齊,參考 str.format()
s.maketrans() 與str.translate()類似
s.partition(t) 返回包含3個字符串的元組一一字符串s在t的最左邊之前的部分、t、字符串s在t之後的部分。如果t不在s內,則返回s與兩個空字符串。使用str.rpartition()可以在t最右邊部分進行分區
s.replace(t,u,n) 返回s的一個副本,其中每個(或最多n個,如果給定)字符串t使用u替換
s.split(t, n) 返回一個字符串列表,要求在字符串t處至多分割n次,如果沒有給定n,就分割儘可能多次,如果t沒有給定,就在空白處分割。使用str.rsplit()可以從右邊進行分割——只有在給定 n並且n小於可能分割的最大次數時才起作用
s.splitlines(f) 返回在行終結符處進行分割產生的行列表,並剝離行終結符(除非f為True)
s.lower() 將s中的字符變為小寫,參見str.upper()
s.strip(chars) 返回s的一個副本,並將開始處與結尾處的空白字符(或字符串chars中的字符)移除,str.lstrip()僅剝離起始處的相應字符,str.rstrip()只剝離結尾處的相應字符
s.swapcase() 返回s的副本,.並將其中大寫字符變為小寫,小寫字符變為大寫,參考str.lower()與str.upper()
s.title() 返回s的副本,並將每個單詞的首字母變為大寫,其他字母都變為小寫,參考str.istitle()
s.translate() 與str.maketrans()類似
s.upper() 返回s的大寫化版本,參考str.lower()
s.zfill(w) 返回s的副本,如果比w短,就在開始處添加0,使其長度為w
字符串是序列,因此也是有大小的對象,我們可以以字符串為參數來使用len() 函數,返回值是字符串中的字符數(如果字符串為空,就返回0)。
我們已經知道,在字符串的操作中,+操作符被重載用於實現字符串連接。如果需要連接大量的字符串,使用str.join()方法是一種更好的方案。該方法以一個序列作為參數(比如,字符串列表或字符串元組),並將其連接在一起存放在一個單獨的字符 串中,並將調用該方法的字符串作為分隔物添加在每兩項之間,例如:
>>> treatises = ["Arithmetica", "Conics", "Elements"]
>>> " ".join(treatises)
'Arithmetica Conics Elements'
>>> "-<>-".join(treatises)
'Arithmetica-<>-Conics-<>-Elements'
>>> "".join(treatises)
'ArithmeticaConicsElements'
第一個實例或許是最常見的,連接一個單獨的字符,這裡是空格。第三個實例純粹是連接,使用空字符串意味著字符串序列在連接時中間不使用任何填充。
str.join()方法也可以與內置的reversed()函數一起使用,以實現對字符串的反轉, 比如,"".join(reversed(s))。當然,通過步距也可以更精確地獲取同樣的結果,比如, s[::-1]。
#*操作符提供了字符串複製功能:
>>> s = "=" * 5
>>> print(s)
=====
>>> s *= 10 #也可以使用複製操作符*的增強版進行賦值
>>> print(s)
==================================================
在用於字符串時,如果成員關係操作符in左邊的字符串參數是右邊字符串參數的 一部分,或者相等,就返回True。
如果我們需要在某個字符串中找到另一個字符串所在的位置,有兩種方法,一種是使用str.index()方法,該方法返回子字符串的索引位置,或者在失敗時產生一個 ValueError異常。另一種是使用str.find()方法,該方法返回子字符串的索引位置,或者在失敗時返回-1。這兩種方法都把要尋找的字符串作為第一個參數,還可以有兩個可選的參數,其中第二個參數是待搜索字符串的起始位置,第三個則是其終點位置。
使用哪種搜索方法純粹是個人愛好與具體場景,儘管如果搜索多個索引位置,使用str.index()方法通常會生成更乾淨的代碼,如下面兩個等價的函數所展示的:
def extract_from_tag(tag, line):
opener = "<" + tag + ">"
closer = "</"+ tag + ">"
try:
i = line.index(opener)
start = i + len(opener)
j = line.index(closer, start)
return line[start:j]
except ValueError:
return None
def extract_from_tag(tag, line):
opener = "<" + tag + ">"
closer = "</"+ tag + ">"
i = line.find(opener)
if i != -1:
start = i + len(opener)
j = line.find(closer, start)
if j != -1:
return line[start:j]
return None
兩個版本的extract_from_tag()函數的作用是完全一致的。比如,extract_from_tag ("red」,」what a <red>rose<red> this is」)返回字符串"rose"。左面這一版本的異常處理部分更清晰地布局在其他代碼之外,明確地表示了如何處理錯誤;右邊版本的錯誤返回值則將分散了錯誤處理的不同情況。
方法 str.count()、str.find()、 str.rfind()、 str.index()、 str.rindex()與 str. startswith()、str.endswith()都接受至多兩個可選的參數:起點位置與終點位置。這裡給出兩個等價的語句,並假定s是一個字符串:
s.count("m",6) == s[6:].count("m")
s.count("m",5, -3) == s[5:-3].count("m")
可以看出,接受起點與終點索引位置作為參數的方法可以運作在由這些索引值指定的字符串分片上。
下面看另一對等價的代碼,主要是為了明確str.partition()的作用:
result=s.rpartition("/")
i = s.rfind("/")
if i == -1:
result = "","",s
else:
result =s[:i],s[i],s[i+1:]
左面的代碼段與右面的代碼段並不完全等價,因為右面還創建了一個新變量i。注意我們可以直接分配元組,而不拘於形式。兩邊的代碼都是搜索/的最右邊出現,如果字符串s ="/usr/local/bin/firefox",那麼兩個代碼段都會產生同樣的結果:('usr/local/bin','/','firefox')。
下面的語句同時使用 str.endswith()與str.lower()來列印文件名 如果該文件是一個JPEG文件:
if filename.lower().endswith((".jpg", ".jpeg")):
print(filename, "is a JPEG image")
>>> "917.5".isdigit(), "".isdigit(), "-2".isdigit(), "203".isdigit()
(False, False, False, True)
is*()方法工作的基礎是Unicode字符分類,比如,以字符串"\N{circled digit two}03" 與"203"為參數調用str.isdigit()都會返回True。出於這一原因,我們不能因為isdigit() 函數返回True就判斷某個字符串可以轉換為整數。
從外部源(其他程序、文件、網絡連接尤其是交互式用戶)接受字符串時,字符串可能包含不需要的開始空白字符與結尾空白字符。我們可以使用str.lstrip()來剝離左邊的空白字符,也可以使用str.rstrip()來剝離右邊的空白字符,或者使用str.strip()同時剝離兩邊的空白字符。我們也可以使用一個字符串作為參數來調用剝離方法,這種情況下,每個字符的每個出現都將被從合適的位置剝離,例如:
>>> s ="\t no parking "
>>> s.lstrip(), s.rstrip(), s.strip()
('no parking ','\t no parking','no parking')
>>> 」<[unbracketed]>".strip("[]<>")
'unbracketed'
>>>"<[unbracketed]>".replace("<[","").replace("]>","")
'unbracketed'
我們可以使用str.replace()方法來在字符串內進行替換。這一方法以兩個字符串作為參數,並返回該字符串的副本(其中第一個字符串的所有出現都被第二個字符串所替代)。如果第二個字符串為空,那麼這一函數的實際效果是刪除第一個字符串的所有出現。
一個頻繁遇到的需求是將字符串分割為一系列子字符串。比如,我們有一個文本文件,需要將其中的數據進行處理,要求每行一個記錄,每個記錄的欄位使用星號進行分隔。為此,可以使用str.split()方法,並以待分割的字符串作為第一個參數,以要分割的最大子數據段數為可選的第二個參數。如果再不指定第二個參數,該方法就會進行儘可能多的分割。下面給出一個實例:
>>> record = "Leo Tolstoy*1828-8-28*1910-11-20"
>>> fields = record.split("*")
>>> fields
['Leo Tolstoy1, '1828-8-28', '1910-11 -20']
以上面的結果為基礎,可以使用str.split()方法對出生日期與死亡日期進行進一步的分割,以便計算其壽命(給定或接受一個年份值)。上面的代碼中,我們必須使用int()方法將年份從字符串轉換為整數,除此之外, 該代碼段是很直接的。我們也可以從fields列表中獲取年份,比如,year_bom = int(fields[1].split("-")[0])。
>>> born = fields[1].split("-")
>>> born
['1828','8','28']
>>> died = fields[2].split("-")
>>> print("lived about", int(died[0]) - int(bom[0]), "years")
lived about 82 years
str.maketrans()方法用於創建字符間映射的轉換表,該方法可以接受一個、兩個或三個參數,但是我們這裡只展示最簡單的(兩個參數)調用方式,其中,第一個參數是一 個字符串,該字符串中的字符需要進行轉換,第二個參數也是一個字符串,其中包含 的字符是轉換的目標,這兩個字符串必須具有相同的長度。str.translate()方法以轉換表作為一個參數,並返回某個字符串根據該轉換表進行轉換後的副本。下面展示了如何將可能包含孟加拉數字的字符串轉換為英文數字:
table ="".maketrans("\N(bengali digit zero}"
"\N(bengali digit one}\N{bengali digit two}"
"\N{bengali digit three}\N(bengali digit four}"
"\N{bengali digit five}\N(bengali digit six)"
"\N{bengali digit seven}\N(bengali digit eight}"
"\N{bengali digit nine}", "0123456789")
print("20749".translate(table)) # prints: 20749
print("\N{bengali digit two}07\N{bengali digit four)"
"\N{bengali digit nine}".translate(table)) # prints: 20749
從上面可以看出,在str.maketrans()調用內部以及第二個print()調用內部,我們利用了 Python的字符串字面值連接,使得字符串跨越了多行,而不需要對換行進行轉義或使用顯示的連接。
我們對空字符串調用了 str.maketrans()方法,因為該方法不關心其針對的具體字符串,而只是對其參數進行處理,並返回一個轉換表*。str.maketrans()方法與str.translate()方法也可以用於刪除字符,方法是將包含待刪除字符的字符串作為第三個參數傳遞給 str.maketrans()。
以上內容部分摘自視頻課程05後端編程Python-4字符串類型(上),更多實操示例請參照視頻講解。跟著張員外講編程,學習更輕鬆,不花錢還能學習真本領。