首頁>技術>

本節是第五講的第四小節,上節為大家介紹了Python語言的數字型別包括(整型、布林型、浮點型、複數型、十進位制型),本節將為大家介紹專案中最常用型別字串型,由於內容較多,分為上下兩部分講解,本篇是下半部分。

使用str.format()方法進行字串格式化

str.format()方法提供了非常靈活而強大的建立字串的途徑。對於簡單的情況,使用str.format()方法是容易的,但是如果需要進行復雜的格式化操作,就要學習該方法需要的格式化語法。

str.format()方法會返回一個新字串,在新字串中,原字串的替換欄位被適當格式化後的引數所替代,例如:

>>> "The novel '{0}' was published in {1}".format("Hard Times", 1854)

"The novel 'Hard Times' was published in 1854"

每個替換欄位都是由包含在花括號中的欄位名標識的。如果欄位名是簡單的整數, 就將被作為傳遞給str.format()方法的一個引數的索引位置。因此,在這種情況下,名為0的欄位被第一個引數所替代,名為1的欄位則被第二個引數所替代。

如果需要在格式化字串中包含花括號,就需要將其複寫,下面給出一個例項:

>>> "{{{0}}} {1} ;-}}".format("I'm in braces", "I'm not")

"{I'm in braces} I'm not ;-}"

如果我們試圖連線字串與數字,那麼Python將產生TypeError異常,但是使用 str.format()方法可以很容易地做到這一點:

>>> ''{0}{1}".format("The amount due is $", 200)

'The amount due is $200'

我們也可以使用str.format()方法連線字串(儘管str.join()方法最適合用於這一目的)。

在上面的例項中,我們使用了一對字串變數,不過在本小節的大部分,我們在str.format()方法的應用例項中都使用字串字面值,這就是為了方便——實際上,任何使用字串字面值的例項中都可以使用字串變數,方法是完全一樣的。

>>> x = "three"

>>> s ="{0}{1}{2}"

>>> s = s.format("The", x, "tops")

>>> s

'The three tops'

#替換欄位可以使用下面的任意一種語法格式:

{field_name}

{field_name!conversion}

{field_name:format_specification}

(field_name!conversion:format_specification}

另外需要注意的一點是,替換欄位本身也可以包含替換欄位,巢狀的替換欄位不能有任何格式,其用途主要是格式化規約的計算。在對格式化規約進行更細緻的解讀時,我們將展示一個例項。現在我們將逐一研究替換欄位的每一個組成部分,首先從欄位名開始。

欄位名(Field Names)

欄位名或者是一個與某個str.format()方法引數對應的整數,或者是方法的某個關鍵字引數的名稱。

>>> "{who} turned {age} this year".format(who="She", age=88)

'She turned 88 this year'

>>> "The {who} was {0} last week".format(12, who="boy")

'The boy was 12 last week'

上面的第一個例項使用了兩個關鍵字引數,分別是who與age,第二個例項使用 了一個位置引數(到這裡為止只在這裡使用過)與一個關鍵字引數。要注意的是,在引數列表中,關鍵字引數總是在位置引數之後,當然,我們可以在格式化字串內部以任何順序使用任何引數。

欄位名可以引用集合資料型別--比如:列表。在這樣的情況下,我們可以包含 一個索引(不是一個分片)來標識特定的資料項:

>>> stock = ["paper", "envelopes", "notepads", "pens", "paper clips"]

>>> "We have {0[1]} and {0[2]} in stock".format(stock)

'We have envelopes and notepads in stock'

0引用的是位置引數,因此,{0[1]}是列表stock引數的第二個資料項,{0[2]}是 列表stock引數的第三個資料項。

#我們也可以存取命名的屬性。假定已經匯入math模組與sys模組

>>> "math.pi=={0.pi} sys.maxunicode=={1.maxunicode}".format(math, sys)

'math.pi==3.141592653589793 sys.maxunicode==1114111'

總而言之,透過欄位名語法,可以引用傳遞給str.format()方法的位置引數與關鍵字引數。如果引數是集合資料型別,比如列表或字典,或引數還包含一些屬性,那麼可以使用[]或.表示法存取所需的部分。

從Python 3.1開始,忽略欄位名成為可能,這種情況下,Python會自動進行處理 (使用從0開始的數值),比如:

>>> "{} {} {}".format("Python", "can", "count")

'Python can count'

當前還在作用範圍內的區域性變數可以透過內建的locals()函式訪問,該函式會返回一個字典,字典的鍵是區域性變數名,字典的值則是對變數值的引用。現在,我們可以使用對映拆分將該字典提供給str.format()方法,對映拆分運算子為**,可應用於對映 (比如字典)來產生一個適合於傳遞給函式的鍵-值列表,比如:

>>> element = "Silver"

>>> number = 47

>>> "Element {number} is {element}".format(**locals())

'Element 47 is Silver'

>>> d=dict(animal="elephant",weight=12000)

>>> "The {animal} weighs {weight}kg".format(**d)

'The elephant weighs 12000kg'

將字典拆分並提供給str.format()方法時,允許使用字典的鍵作為欄位名。這使得字串格式更易於理解,也易於維護,因為不需要依賴於引數的順序。然而,要注意的是,如果需要將不止一個引數傳遞給str.format(),那麼只有最後一個引數才可以使用對映拆分。

轉換(Conversions)

>>> decimal.Decimal("3.4084")

Decimal('3.4084')

>>> print(decimaI.Decimal("3.4084"))

3.4084

decimal.Decimal的第一種展示方式是其表象形式,這種形式的用途是提供一個字串—該字串被Python解釋時將重建其表示的物件。Python程式可以評價Python程式碼段或整個程式,因此,這種表象形式有時候是有用的。不是所有物件都可以提供這種便於重建的表象形式,如果提供,其形式為包含在尖括號中的字串,比如,sys 模組的表象形式為字串”<module 'sys' (built-in)>"。

第二種是以字串形式對decimal.Decimal進行展示的,這種形式的目標是便於閱讀,因此其著眼點是展示一些讀者感興趣的東西。如果某種資料型別沒有字串表示形式,但又需要使用字串進行表示,那麼Python將使用表象形式。

Python內建的資料型別都知道str.format()方法,在作為引數傳遞給這一方法時, 將返回一個適當的字串來展示自己。此外,重寫資料型別的通常行為並強制其提供字串形式或表象形式也是可能的,這是透過向欄位中新增轉換(conversion)指定符實現的。目前,有3個這樣的指定符:s用於強制使用字串形式;r用於強制使用表象形式; a用於強制使用表象形式,但僅限於ASCII字元。下面給出一個例項:

>>> "{0} {0!s} {0!r} {0!a}".format(decimal.Decimal("93.4"))

"93.4 93.4 Decimal('93.4') Decimal('93.4')"

在上面的例項中,decimal.Decimal的字串形式產生的字串與提供給str.format()的字串是相同的。同時,在這個比較特定的例項中,由於都只使用ASCII 字元,因此,表象形式與ASCII表象形式之間沒有區別。

到這裡,我們講述瞭如何將變數值放置在格式化字串中,以及如何強制使用字串形式或表象形式。現在,我們開始考慮值本身的格式問題。

格式規約(Format Specifications)

整數、浮點數以及字串的預設格式通常都足以滿足要求,但是如果需要實施更精確的控制,我們就可以透過格式規約很容易地實現。

對於字串而言,我們可以控制的包括填充字元、欄位內對齊方式以及欄位寬度的最小值與最大值。

字串格式規約是使用冒號(:)引入的,其後跟隨可選的字元對一個填充字元與一個對齊字元(<用於左對齊,^用於中間對齊,>用於右對齊), 之後跟隨的是可選的最小寬度(整數),如果需要指定最大寬度,就在其後使用句點,句點後跟隨一個整數值。

要注意的是,如果我們指定了一個填充字元,就必須同時指定對齊字元。我們忽略了格式規約的符號與型別部分,因為對字串沒有實際影響。只使用一個冒號而沒有任何其他可選的元素是無害的,但也是無用的。

>>> s ="The sword of truth"

>>> "{0}".format(s) # default formatting

'The sword of truth'

>>> "{0:25}".format(s) # minimum width 25

'The sword of truth '

>>> "{0:>25}".format(s) # right align, minimum width 25

' The sword of truth'

>>> "{0:^25}".format(s) # center align, minimum width 25

' The sword of truth '

>>> ”{0:-^25}".format(s) # - fill, center align, minimum width 25

'---The sword of truth----'

>>>"{0:.<25}".format(s) # . fill, left align, minimum width 25

'The sword of truth.......'

>>> "{0:.10}".format(s) # maximum width 10

'The sword '

在倒數第二個例項中,我們必須指定左對齊(即便這是預設的)。如果漏掉了<, 就將得到:.25,這只是意味著最大欄位寬度為25個字元。

前面我們已經注意到,在格式化規約內部包括替換欄位是有可能的,從而有可計算的格式也是可能的。比如,這裡給出了使用maxwidth變數設定字串最大寬度的兩種方式,第一種方法使用標準的字串分片,第二種方法使用內部替換欄位。

>>> maxwidth = 12

>>> "{0}".format(s[:maxwidth])

'The sword of'

>>> "{0:.{1}}".format(s, maxwidth)

'The sword of'

對於整數,透過格式規約,可以控制填充字元、欄位內對齊、符號、最小欄位寬度、基數等。整數格式規約以冒號開始,其後可以跟隨一個可選的字元對---一個填充字元與一個對齊字元(<用於左對齊,^用於中間對齊,>用於右對齊,=用於在符號與數字之間進行填充),之後跟隨的是可選的符號字元:+表示必須輸出符號,- 表示只輸出負數符號,空格表示為正數輸出空格;為負數輸出符號-。再之後跟隨的是可選的最小寬度整數值——其前可以使用字元#引導,以便獲取某種基數進製為字首的輸出(對二進位制、八進位制、十六進位制數值),也可以以0引導,以便在對齊時使用0 進行填充。如果希望輸出其他進位制資料,而非十進位制數,就必須新增一個型別字元 —b用於表示二進位制,o用於表示八進位制,x用於表示小寫十六進位制,X用於表示大寫十六進位制,為了完整性,也可以使用d表示十進位制整數。此外,還有兩個其他型別字元:c表示輸出整數對應的Unicode字元;n表示以場所敏感的方式輸出數字。

>>> "{0:0=12}".format(8749203) # 0 fill, minimum width 12

'000008749203'

>>> "{0:0=12}".format(-8749203) # 0 fill, minimum width 12

'-00008749203'

>>> "{0:012}".format(8749203) # 0-pad and minimum width 12

'000008749203'

>>> "{0:012}".format(-8749203) # 0-pad and minimum width 12

'-00008749203'

前兩個例項使用的填充字元為0,填充位置在符號與數字本身之間(=);後兩個例項要求最小寬度為12,並使用0進行填充。

# 下面給出了一些對齊例項:

>>>"{0:*<15}".format(18340427) # * fill,left align, min width 15

‘18340427*******’

>>> "{0:*>15}".format(18340427) # * fill,right align, min width 15

'*******18340427'

>>> "{0:*^15}".format(18340427) # * fill,center align, min width 15

'***18340427****'

>>> "{0:*^15}".format(-18340427) # * fill, center align, min width 15

'***-18340427***'

#下面給出一些展示符號字元作用的例項:

>>> "[{0:}] [{1:}]".format(539802, -539802) # space or -sign

'[539802] [-539802]'

>>> "[{0:+}] [{1:+}]".format(539802, -539802) # force sign

'[+539802] [-539802]'

>>> "[{0:-}] [{1:-}]”.format(539802, -539802) # - sign if needed

'[539802] [-539802]'

#下面是兩個使用某些型別字元的例項:

>>> "{0:b} {0:o} {0:x} {0:X}".format(14613198)

'110111101111101011001110 67575316 deface DEFACE'

>>>”{0:#b} {0:#o} {0:#x} {0:#X}".format(14613198)

'0b110111101111101011001110 0o67575316 0xdeface 0XDEFACE'

為整數指定最大欄位寬度是不可能的,這是因為,這樣做要求數字是可裁剪的, 並可能會使整數沒有意義。如果我們使用Python 3.1,並在格式規範中使用一個逗號,則整數將使用逗號進行分組。例如:

>>> ”{0:,} {0:*>13,}".format(int(239432185e6))

'239,432,185,000,000 239,432,185,000,000'

最後一個可用於整數(也可用於浮點數)的格式化字元是n。在給定的字元是整數時,其作用與d相同;在給定的字元是浮點數時,其作用與g相同。n的特殊之處在於,充分考慮了當前的場所,並在其產生的輸出資訊中使用場所特定的十進位制字元與分組字元。預設的場所稱為C場所,對這種C場所,十進位制字元是一個句點,分組字元是一個空字串。

在程式的起始處新增下面兩行,並將其作為最先執行的語句, 透過這種方式,可以充分考慮不同使用者的場所:

import locale

locale.setlocale(locale.LC_ALL,”“)

如果將空字串作為場所傳遞,那麼Python會嘗試自動確定使用者的場所(比如, 透過檢查LANG環境變數),並以C場所為預設場所。下面給出一些例項,展示了對整數與浮點數使用不同場所的影響:

x,y = (1234567890, 1234.56)

locale.setlocale(locale.LC_ALL, “C")

c = "{0:n} {1:n}".format(x, y) # c == "1234567890 1234.56”

locale.setlocale(locale.LC_ALL, "en_US.UTF-8")

en = ”{0:n} {1:n}”.format(x, y) # en == "1,234,567,890 1,234.56”

locale.setlocale(locale.LC_ALL, "de_DE.UTF-8")

de = "{0:n} {1:n}".format(x, y) # de == "1.234.567.890 1.234,56"

雖然n對於整數非常有用,但是對於浮點數的用途有限,因為隨著浮點數的增大, 就會使用指數形式對其進行輸出。

對於浮點數,透過格式規約,可以控制填充字元、欄位對齊、符號、最小欄位寬度、十進位制小數點後的數字個數,以及是以標準形式、指數形式還是以百分數的形式輸出數字。

用於浮點數的格式規約與用於整數的格式規約是一樣的,只是在結尾處有兩個差別。在可選的最小寬度後面,透過寫一個句點並在其後跟隨一個整數,我們可以指定在小數點後跟隨的數字個數。我們也可以在結尾處新增一個型別字元:e表示使用小寫字母e的指數形式,E表示使用大寫字母E的指數形式,f表示標準的浮點形式,g 表示“通常”格式一一這與f的作用是相同的,除非數字特別大(在這種情況下與e 的作用相同——以及幾乎與g等同的G,但總是使用f或E)。另一個可以使用的是 %-這會導致數字擴大100倍,產生的數字結果使用f並附加一個%字元的格式輸出。

下面給出幾個例項,展示了指數形式與標準形式:

>>> amount = (10 ** 3)*math.pi

>>> "[{0:12.2e}] [{0:12.2f}]".format(amount)

'[ 3.14e+03] [ 3141.59]'

>>>"[{0:>12.2e}] [{0:*>12.2f}]".format(amount)

'[ 3.14e+03] [*****3141.59]'

>>> "[{0:*>+12.2e}] [{0:*>+12.2f}]".format(amount)

'[***+3.14e+03] [****+3141.59]'

>>> "{:,.6f}".format(decimal.Decimal("1234567890.1234567890"))

'1,234,567,890.123457'

第一個例項中最小寬度為12個字元,在十進位制小數點之後有2個數字。第二個例項構建在第一個例項之上,添加了一個填充字元*,由於使用了填充字元就必須同時也使用對齊字元,因此指定了右對齊方式(雖然對數字而言這是預設的)。第三個例項構建在前兩個例項之上,添加了符號運算子+,以便在輸出中使用符號。

字元編碼

本質上說,計算機只能儲存位元組,即8位元的值,如果是無符號數,那麼取值範圍從 0x00到0xFF,每個字元必須都以某種形式的位元組表示。在計算機技術的早期,研究者們設計的編碼機制是使用一個特定位元組表示某個特定的字元。例如,使用ASCII編碼,就用0x41 表示A,用0x42表示B,依此類推。在西歐,通常使用的是Latin-1編碼,其前127個字 符與7位元ASCII相同,其餘部分則用於重音字元與歐洲人需要的其他符號。在計算機科學的發展中,研究者還設計了很多種其他編碼方式,其中的大部分仍然在使用中。遺憾的是,存在太多的編碼方式會帶來很多不便,在編寫國際化軟體時更是如此。 一個幾乎被最廣泛採納的標準是Unicode編碼,在這種編碼中,Unicode為每個字元分配一個整數,即字元,就像早期的編碼方式一樣,但是Unicode不侷限於使用一個位元組表示每個字元,因而有能力使用這種編碼方式表示每種語言中的每個字元。並且, 作為對其表示能力的一種增強,其中的前127個Unicode字元與7位元ASCII表示的前127個字元是相同的。

Unicode是如何儲存的?當前,定義了超過100萬個Unicode字元,因此,即便使用有符號數字,一個32位整數也足以存放任何Unicode字元,因此,最簡單的用於儲存Unicode字元的方式是使用32位整數序列,每個整數代表一個字在記憶體中, 這是非常便利的,因為我們可以設計一個32位整數陣列,陣列中的每個元素與某個字元有一對一的對應關係。但對檔案或透過網路連線傳送的文字而言,尤其是在文字幾乎都是7位元ASCII時,每個整數的4個位元組中最多有3個位元組會是0x00。為避免這樣的浪費,Unicode自身有幾種表示方式。

在記憶體中,Unicode通常以UCS-2格式(實質上是16位元的無符號整數)表示前 65535個字元,或者以USC-4格式(32位整數)表示所有的字元——本書寫作時,共 有1114111個。在Python編譯時,會設定為使用某一種格式(如果sys.maxunicode為65535, Python 編譯時就使用 UCS-2)。

對存放在檔案中或透過網路連線傳送的資料,情況會更加複雜。如果使用了 Unicode, 那麼字元可以使用UTF-8進行編一這種編碼中,對前127個字元,每個字元使用一個位元組表示;對其他字元,則使用兩個或更多的位元組數來表示每個字元。對英文文字而言, UTF-8是非常緊湊的,如果只使用了 7位元字元,則UTF-8檔案與ASCII檔案實質上是 一樣的。另一種常見的編碼方式是UTF-16編碼,這種編碼方式中,對大多數字符使用兩個位元組表示,對其他的一些字元則使用4個位元組表示。對某些亞洲語言,這種編碼方式比 UTF-8更緊湊,但與UTF-8不同的是,UTF-16文字應該以一個位元組順序標記開始,以便 用於讀取該文字的程式碼可以判定位元組對是big-endian還是little-endian。此外,所有舊的編碼格式,比如GB2312、ISO-8859-5、Latin-1等,實際上都在常規的使用中。

str.encode()方法可以返回一個位元組序列 實際上是一個bytes物件,編碼時根據我們提供的編碼引數進行編碼。使用這一方法,可以更好地理解不同編碼格式之間的差別,以及為什麼進行錯誤的編碼假設或導致錯誤。在引號之前使用一個字母b,表示使用的是位元組字面值,而非字串字面值。作為一種便利,在建立位元組字面值時,我們可以混合使用可列印的ASCII字元與十六進位制跳脫字元。

>>> artist ="Tage Asén"

>>> artist.encode("Latin1")

b'Tage As\xe9n'

>>> artist.encode("CP850")

b'Tage As\x82n'

>>> artist.encode("utf8")

b'Tage As\xc3\xa9n'

>>> artist.encode("utf16")

b'\xff\xfeT\x00a\x00g\x00e\x00 \x00A\x00s\x00\xe9\x00n\x00'

我們不能使用ASCII編碼方式對Tage Asén的名稱進行編碼,因為這種編碼不包括任意重音字元,因此,這樣做會導致產生一個UnicodeEncodeError異常。 Latin-1編碼(即ISO-8859-1)是一種8位元的編碼格式,其中包含了這一名稱所需要的所有字元。另一方面,Ern B'nk這一 artist不夠幸運,因為字元不是一個Latin-1字 符,不能成功進行編碼。當然,這兩個名稱都可以使用Unicode編碼格式進行編碼。 要注意的是,對UTF-16而言,頭兩個位元組表示的位元組順序標記一解碼函式會根據這一標記確定資料是big-endian還是little-endian,以便分別進行相應的處理。

對str.encode()方法,還有兩點值得注意。第一個引數(編碼名稱)是大小寫不敏感的,連字元與下劃線在其中是等同對待的,因此,"us-ascii"與"US_ASCH"被認為是相同的。還有很多別名,比如,"latin"、"latinl"、"latin_ 1 "、"ISO-8859-1 "、"CP819", 其他一些都是“Latin-1”。該方法也可以接受可選的第二個引數,其作用是指定錯誤處理方式。例如,如果第二個引數為“ignore”或“replace”,那麼可以將任何字串編碼為ASCII格式——當然,這會導致資料丟失——也可以無丟失的情況,如果我們使用“backslashreplace”,就會使用\x、\u與\U等跳脫字元替換非ASCII字元。例如, artist.encode("ascii", "ignore")會產生 b'Tage Asn', artist.encode("ascii", "replace")會產生b'Tage As?n',而 artist.encode("ascii", "backslashreplace")會產生 b'Tage As\\xe9n' (我們也可以使"{0!a)".format(artist)得到一個 ASCII 字串"'Tage As\\xe9n'")

str.encode()方法的 complement 是 bytes.decode()(以及 bytearray.decode()),該方法 將返回一個字串,其中使用給定的編碼格式對位元組進行解碼,例如:

>>> print(b"Tage \xc3\x85s\xc3\xa9n".decode("utf8"))

Tage Åsén

>>> print(b"Tage \xc5s\xe9n".decode("latin1”))

Tage Åsén

8位元Latin-1、CP850 (一種IBM PC編碼格式)以及UTF-8編碼之間的細弱差別使得猜測編碼格式不太可行,幸運的是,UTF-8正在成為明文文字檔案編碼格式的事實標準,因此,後人只需要熟悉這一格式,甚至不需要知道曾經存在過其他編碼格式。

Python的.py檔案使用UTF-8編碼,因此,Python總是知道字串字面值要使用的編碼格式。這意味著,我們可以在字串中輸入任意的Unicode字元——只要使用的編輯器支援。

在從外部源(比如socket)讀取資料時,Python無法知道其使用的編碼格式,因此會返回位元組序列,並由程式設計師對其進行相應的解碼。對文字檔案,Python釆用一種更軟化的方法,即使用本地編碼——除非明確指定編碼格式。

13
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • Python實用程式碼-無限級分類樹狀結構生成演算法