Python高效編程之88條軍規(1):編碼規範、字節序列與字符串

2021-03-02 極客起源
在微信公眾號「極客起源」中輸入595586,可學習全部的《Python高效編程之88條軍規》系列文章。用程式語言寫代碼是自由的,編譯器不會強制你使用特定的格式編寫程序(只要符合語法,編譯器才不管你呢!)。所以很多程式設計師就會將Python當做自己熟悉的Java、C++等語言來用。不過這些編碼方式真的是最好的選擇嗎?本系列文章將為你揭秘88種在編寫Python代碼中的規則,這些規則將會讓你Python程序更加健壯,運行效率更高。Python的PEP 8是Python官方提供了關於如何格式化Python代碼的樣式指南。儘管可以用任何有效的方式編寫Python代碼,但是,使用一致的樣式會使你的代碼更易於訪問和閱讀,以及與其他Python程式設計師使用同一種樣式有助於項目上的分工協作。即使你是唯一會閱讀代碼的人,遵循樣式指南也可以讓你的代碼更容易維護,並可以避免許多常見錯誤。關於PEP 8的詳細內容,讀者可以查看下面的頁面:https://www.python.org/dev/peps/pep-0008/在Python語言中,空格是有實際意義的。Python程式設計師應該更關注空格的用法,下面是與空格相關的一些建議(並不一定要遵守,但按照這個規範,會讓你的Python程序看著更舒服):(2)儘管縮進可以使用任意多個空格,但建議統一使用4個空格進行縮進;(3)每行不應該有過多的字符,建議最多不要超過79的字符;(4)如果每行的字符過多(超過79個),應該折到下一行,而且應該在當前縮進的基礎上再使用4個空格進行縮進,如下圖所示:

(5)在文件中,如果函數和類相鄰,建議使用兩個空行將他們分開,這樣會讓代碼一目了然;(7)在字典中,不要在key和冒號(:)之間放置空格,如果對應的值與key和冒號在同一行,應該在值前面放置一個空格;(8)在變量賦值時,等號(=)前面和後面應該有一個空格;(9)對於類型注釋(type annotations),要確保變量和冒號直接沒有空格,而且要在類型信息前面使用一個空格,如下圖所示:

PEP 8程序的不同部分採用統一的命名風格。因為擁有統一風格的命名,會讓代碼更容易閱讀,下面是一些推薦的命名規則:(1)函數、變量和屬性應該使用小寫字母加下劃線(_)的風格,例如,get_name,product_id等;(2)被保護的(protected)的實例屬性應該在名字前面加一個下劃線,例如,_name,_product_id等;(3)私有(private)實例屬性應該在名字前面加兩個下劃線(__),例如,__name,__product_id等;(4)類名應該使用大駝峰格式,也就是每一個單詞首字母都要大寫,例如,MyClass,Test,Product等;(5)模塊層常量的名字所有的字母都應該大寫,如果包含多個單詞,中間用下劃線分隔,例如,PRODUCT_ID,OS_PATH等;(6)類中的實例方法的第1個參數應該使用self(儘管可以使用任意參數名,但推薦使用self),該參數引用了對象本身;(7)類方法的第1個參數應該使用cls(儘管可以使用任意參數名,但推薦使用cls),該參數引用了類的本身;Python禪宗指出:「應該有一種(最好只有一種)明顯的方式來完成你的工作。」。PEP 8正常嘗試按這個規則確定表達式和語句的編寫風格。(1)使用內聯求反(if a is not b)代替對正表達式的求反(if not a is b);(2)如果要判斷序列(字符串、列表、字典等)是否為空(是否有元素),並不建議通過序列長度是否為0來判斷(if len(somelist) == 0),而要直接使用not進行判斷,例如,if not somelist。如果somelist是空串或空序列,那麼not somelist就為True,當然,如果somelist不為空,那麼somelist就被認為是True;(3)儘量避免單行的if、for和while語句,除非是複合語句,否則為了清晰起見,應該將它們分布在多行;(4)如果表達式過長,建議將這樣的表達式分布在多行,這樣更容易閱讀。但注意要在每行結尾加連接符,並且從第2行開始在第1行的基礎上再往後縮進4個空格;(1)將import語句(包括from x import y和import x語句)放在文件的最頂端;(2)如果在import語句中涉及到模塊名,應該使用絕對的模塊名,而不要使用相對的模塊名。例如,為了從bar包導入foo模塊,應該使用from bar import foo,而不要使用Import foo;(3)如果必須要使用相對的模塊名,應該顯式使用from . import foo形式;軍規2:了解字節序列(bytes)和字符串(str)的差異在Python語言中,有兩個數據類型可以表示字符序列:字節序列和字符串。其中字節序列中包含了原始的,8位無符號的值,通常以ASCII編碼形式顯示:如果用字節序列表示字符序列,應該以b開頭,代碼如下:
a = b'h\x65llo'print(list(a))print(a)

[104, 101, 108, 108, 111]b'hello'

字符串的實例包含了Unicode編碼,這些編碼表示人類語言的文本字符:
a = 'a\u0300 propos'print(list(a))print(a)

['a', '̀', ' ', 'p', 'r', 'o', 'p', 'o', 's']à propos

值得注意的是,字符串並不包含與之關聯的二進位編碼,而字節序列也不包含與之關聯的文本編碼。為了將文本編碼數據轉換為二進位數據,必須調用字符串的encode方法。為了將二進位數據轉換為文本編碼數據,必須調用字節序列的decode方法。我們可以顯式地指定這些方法的編碼格式,或者接受這些方法的默認編碼格式。默認編碼格式通常是UTF-8,不過也並不是所有方法的默認編碼格式都是UTF-8,具體情況請看下面的內容。在編寫Python程序時,在例接口最遠的邊界(也就是最初接觸Unicode數據的地方)進行Unicode數據的編碼和解碼非常重要。這種方法通常被稱為Unicode三明治。程序的核心應使用包含Unicode數據的str類型,並且不應對字符編碼做任何假設。這種方法使你可以非常容易接受其他文本編碼(例如Latin-1,Shift JIS和Big5),同時嚴格限制輸出文本編碼(理想情況下為UTF-8)。字符類型之間的分拆將導致Python代碼中出現兩種常見情況:(1)操作的是包含UTF-8編碼(或其他編碼)的8位字節序列;(2)操作的是沒有特定編碼的Unicode字符串;
def to_str(bytes_or_str):    if isinstance(bytes_or_str, bytes):                value = bytes_or_str.decode('utf-8')    else:                value = bytes_or_str    return value   print(repr(to_str(b'hello')))print(repr(to_str('world')))

def to_bytes(bytes_or_str):    if isinstance(bytes_or_str, str):        value = bytes_or_str.encode('utf-8')    else:        value = bytes_or_str    return value  print(repr(to_bytes(b'hello')))print(repr(to_bytes('world')))

在Python中處理原始8位值和Unicode字符串時,有兩個大陷阱。第一個問題是字節和字符串的工作方式看似相同,但是它們的實例彼此並不兼容,因此你必須仔細考慮要傳遞的字符序列的類型。字節序列與字符串都支持加號(+)運算,也就是說,可以用加號分別將字節序列和字符串連接起來,看下面的代碼:
print(b'hello ' + b' world')print('hello ' + 'world')

b'hello  world'hello world

但是不能將字節序列和字符串相加,例如,下面的代碼會拋出異常:
print(b'hello ' + 'world')

Traceback (most recent call last):  File "/python/bytes_str.py", line 36, in <module>    print(b'hello ' + 'world')TypeError: can't concat str to bytes

print('hello ' + b'world')

Traceback (most recent call last):  File "/python/bytes_str.py", line 37, in <module>    print('hello ' + b'world')  TypeError: can only concatenate str (not "bytes") to str

如果兩側的操作數都是字節序列或字符串,那麼也可以用於邏輯比較(<、<=、>、>=等運算符)。
print('hello' > 'world')print(b'hello' < b'world')

與加號類似,字符串與字節序列不能直接比較,如下面的代碼會拋出異常:
print(b'hello' > 'world')

Traceback (most recent call last):  File "/python/bytes_str.py", line 41, in <module>    print(b'hello' > 'world')TypeError: '>' not supported between instances of 'bytes' and 'str'

與=、<、<=、>=、>=這些運算符不同,字節序列和字符串可以直接使用「==」判定是否相等。不過這是個陷阱,就算字節序列和字符串表面上每一個字符都是相同的,返回的結果仍然是False。
print(b'hello' == 'hello')

print(b'hello %s' % b'world')print('hello %s' % 'world')

b'hello world'hello world

但是不能傳遞字符串到字節序列中(反過來可以),因為Python並不清楚使用何種編碼格式將字符串轉換為字節序列:
print('hello %s' % b'world')  print(b'hello %s' % 'world')  

Traceback (most recent call last):  File "/python/bytes_str.py", line 50, in <module>    print(b'hello %s' % 'world') TypeError: %b requires a bytes-like object, or an object that implements __bytes__, not 'str'

第2個問題是涉及文件句柄的操作(由打開的內置函數返回),寫文件時默認Unicode字符串而不是字節序列。這可能會導致拋出異常,尤其是對於習慣了Python 2的程式設計師而言。例如,假設我要向文件中寫入一些二進位數據,下面的代碼會拋出異常:
with open('data.bin', 'w') as f:    f.write(b'\xf1\xf2\xf3\xf4\xf5')

Traceback (most recent call last):  File "/python/bytes_str.py", line 53, in <module>    f.write(b'\xf1\xf2\xf3\xf4\xf5')TypeError: write() argument must be str, not bytes

拋出異常的原因是該文件是以寫文本模式('w')而不是寫二進位模式('wb')打開的。當文件處於文本模式時,寫操作期望字符串包含Unicode數據,而不是字節序列。所以為了避免拋出異常,應該用「wb」模式打開data.bin文件。
with open('data.bin', 'wb') as f:    f.write(b'\xf1\xf2\xf3\xf4\xf5')

從文件讀取數據也存在類似的問題。例如,下面的代碼嘗試讀取data.bin文件的內容:
with open('data.bin', 'r') as f:   data = f.read()

Traceback (most recent call last):  File "/python/bytes_str.py", line 61, in <module>    data = f.read()  File "/Users/lining/opt/anaconda3/lib/python3.7/codecs.py", line 322, in decode    (result, consumed) = self._buffer_decode(data, self.errors, final)UnicodeDecodeError: 'utf-8' codec can't decode byte 0xf1 in position 0: invalid continuation byte

失敗是因為文件是在讀取文本模式('r')而非讀取二進位模式('rb')中打開的。當句柄處於文本模式時,它將使用系統的默認文本編碼來使用bytes.encode(用於寫入)和str.decode(用於讀取)方法來解釋二進位數據。在大多數系統上,默認編碼為UTF-8,該編碼不能接受二進位數據b'\ xf1 \ xf2 \ xf3 \ xf4 \ xf5',因此會拋出異常。所以應該使用「rb」模式來打開二進位文件。
with open('data.bin', 'rb') as f:    data = f.read()    assert data == b'\xf1\xf2\xf3\xf4\xf5'  

另外,還可以為open函數明確指定encoding參數(編碼格式),以確保Python可以正確處理二進位的編碼格式。例如,下面的代碼明確指定了使用cp1252編碼格式以只讀方式打開data.bin文件。
with open('data.bin', 'r', encoding='cp1252') as f:    data = f.read()    print(data)

(1)字節序列(bytes)包含8位的二進位數據,字符串(str)包含Unicode編碼的值;(2)為了讓程序更健壯,需要使用專門的函數來校驗輸入的是字節序列,還是字符串。如前面的to_bytes函數和to_str函數;(3)字節序列和字符串不能混合在一起進行運算(如+、>、<、%等);(4)如果你想讀寫二進位格式的文件,應該使用二進位模式打開文件(例如,"rb"或"wb");(5)如果你想讀寫文本格式的文件,需要考慮文本的編碼格式。需要顯式通過encoding參數傳入正確的編碼格式;對本文感興趣,可以加李寧老師微信公眾號(unitymarvel):

關注  「極客起源」  公眾號,獲得更多免費技術視頻和文章。

相關焦點

  • 你真的知道 Python的 字符串是什麼嗎?
    (如列表、元組)的不同之處在於,它的「元素」限定了只能是Unicode碼點。因為它是可變長度的編碼方案,針對不同的字符使用不同的字節數來編碼,例如編碼英文字母時,只需要一個字節(8個比特),而編碼較複雜的漢字時,就會用到三個字節(24個比特)。
  • Python 編碼為什麼那麼蛋疼?
    str 到 unicode 之間的轉換用哪 decode 還是 encode 方法還特不好記,老是混淆,問題究竟出在哪裡?字符 「禪」 有可能是以 「11100111 10100110 10000101」 佔用3個字節的長度存儲,為什麼說是有可能呢?這個放到後面再說。Python 編碼為什麼那麼蛋疼?當然,這不能怪開發者。這是因為 Python2 使用 ASCII 字符編碼作為默認編碼方式,而 ASCII 不能處理中文,那麼為什麼不用 UTf-8 呢?
  • 給妹子講python-S01E07字符編碼歷史觀:從ASCII到Unicode
    容器遍歷和列表解析式給妹子講python-S01E05字符串的基本用法給妹子講python-S01E06字符串用法進階【要點搶先看】1.字符編碼與解碼的概念2.ASCII編碼到Unicode編碼的發展過程3.容易混淆的字符編碼與字符代碼【妹子說】上兩集基本上讓我們熟悉了字符串的常見用法
  • 給妹子講python-S01E08理清python中的字符編碼方法
    容器遍歷和列表解析式給妹子講python-S01E05字符串的基本用法給妹子講python-S01E06字符串用法進階給妹子講python-S01E07字符編碼歷史觀:從ASCII到Unicode【要點搶先看】1.python中編、解碼的本質是文本字符串和字節字符串的相互轉換
  • Python3 是如何解決棘手的字符編碼問題的?
    題圖:unsplash.comPython3 最重要的一項改進之一就是解決了 Python2 中字符串與字符編碼遺留下來的這個大坑。>>> b"a"+b"c"b'ac'>>> b"a"*2b'aa'>>> b"abcdef\xd6"[1:]b'bcdef\xd6'>>> b"abcdef\xd6"[-1]214>>> b"a" + "b"Traceback (most recent call last):  File "<
  • Python 2.x 字符編碼終極指南
    但僅了解這篇文章的內容,並不能幫我們在日常編程中躲過一些字符編碼相關的坑,Stackoverflow 上就有大量編碼相關的問題,比如 1,2,3。本文首先嘗試對編碼、解碼進行一個宏觀、直觀的解讀,然後詳細來解釋 python2 中的str和unicode,並對常見的UnicodeEncodeError 和 UnicodeDecodeError 異常進行剖析。
  • python字符的編碼與解碼
    什麼是字符編碼計算機裡面是由各種電子電路組成的,它是如何識別我們的寫的字符的,比如hello ,你,我。直接識別是不可能,它只能識別 二進位的0,1字符。所有我們輸入進去的字符,最終都會被轉化成0,1這種組合在一起的一串數字。
  • Python基礎:數據類型和變量&字符串和編碼
    Python基礎:2.字符串和編碼字符編碼我們已經講過了,字符串也是一種數據類型,但是,字符串比較特殊的是還有一個編碼問題。因為計算機只能處理數字,如果要處理文本,就必須先把文本轉換為數字才能處理。Unicode把所有語言都統一到一套編碼裡,這樣就不會再有亂碼問題了。Unicode標準也在不斷發展,但最常用的是用兩個字節表示一個字符(如果要用到非常偏僻的字符,就需要4個字節)。現代作業系統和大多數程式語言都直接支持Unicode。現在,捋一捋ASCII編碼和Unicode編碼的區別:ASCII編碼是1個字節,而Unicode編碼通常是2個字節。
  • Python中字符串編碼在二進位之間相互轉換的方法
    ,關注我,一同學習簡單易懂的Python編程。第八十節:字符串編碼轉換在學習「計算字符串的長度」(詳見第72節內容1967年ASCII第一次以規範標準的類型發表以來,到今天它已經走過了53年的風雨歷程,作為一種國際通用的西文字符編碼標準,它的作用的不可忽視的,但隨著技術的發展,由於ASCII碼自身的局限性,它已逐漸被後起之秀-萬國碼「utf-8」所取代。
  • Fluent Python - Part4 文本和字節序列
    人類使用文本,計算機使用字節序列--- Esther Nam 和 Travis Fischer本章內容基本沒啥用,平常也基本接觸不到多編碼類型的文本處理
  • 深入剖析go中字符串的編碼問題——特殊字符的string怎麼轉byte?
    Go中的HTTP請求之——HTTP1.1請求流程分析,所以這兩天本來打算研究HTTP2.0的請求源碼翻譯整理過來其實也就是兩點:go中的代碼總是用utf8編碼,並且字符串能夠存儲任何字節。沒有經過字節級別的轉義,那麼字符串是一個標準的utf8序列。
  • Python中如何計算字符串的長度
    ,關注我,一同學習簡單易懂的Python編程。第七十二節:計算字符串長度在學習計算字符串的長度之前,需要先了解一個概念:字符編碼。電腦代碼運行中我們現在常用的字符編碼有這幾種:1、GB2312:就是按照我國的國家標準第2312條編碼,其中是不包含繁體字的;
  • 【Python基礎】(6.1)字符編碼
    目錄一 引入二 知識儲備三、字符編碼介紹3.1 什麼是字符編碼?3.2 字符編碼表的發展史 (了解)3.3 編碼與解碼4.1 文本編輯器nodpad++存取文本文件 ✦ ✦ ✦ ✦ ✦ ✦一 引入    字符串類型、文本文件的內容都是由字符組成的,但凡涉及到字符的存取,都需要考慮字符編碼的問題。
  • 零基礎如何使用python處理字符串?
    字符串是作為任意一門程式語言的基礎,在Python中的關鍵字叫做str。而本文對字符串的操作都是建立在數據是字符串而言,可以通過內置函數type()來測試一組數據是否是字符串。我相信python開發者不會為一個作用創造出好幾種方法。主要區別是isdigit可以檢測字節中的數字,isnumeric可以檢測其他語言的數字,如中文。
  • bytes對象,不可變的字節序列
    不少程式語言中的 字符串 都是由 字符數組 (或稱為 字節序列
  • 深入淺出 + 徹底理解 Python 編碼
    本文的目的是簡明扼要地說明python的編碼機制,並給出一些建議。問題1:問題在哪裡?問題是我們的靶子,心中沒有問題去學習就會抓不住重點。本文使用的編程環境是centos6.7,python2.7。程序看到這個01串被雙引號包圍著,自然知道這個01串是一個字符串。然後這個字符串被賦值給了s。到此,就是第一句的執行邏輯。現在繼續進行第二句的執行。e = s.encode("utf-8")的意思是將字符串s用utf-8進行編碼,並將編碼後的字符串賦值給e。
  • Python拼接字符串的七種方式
    公眾號Python貓, 專注python技術、數據科學和深度學習,力圖創造一個有趣又有用的學習分享平臺。忘了在哪看到一位編程大牛調侃,他說程式設計師每天就做兩件事,其中之一就是處理字符串。相信不少同學會有同感。幾乎任何一種程式語言,都把字符串列為最基礎和不可或缺的數據類型。而拼接字符串是必備的一種技能。今天,我跟大家一起來學習Python拼接字符串的七種方式。
  • Python bytes類型及用法
    Python bytes 類型用來表示一個字節串。「字節串「不是編程術語,是我自己「捏造」的一個詞,用來和字符串相呼應。
  • python基礎學習—04字符串與編碼
    字節是計算機存儲的最小單位,具體換算如下:UTF32是Unicode 碼 的一種實現,一般用4個字節表示一個字符,而一個英文字符只需要1個字節,一個常用漢字需要2個字節,這樣比較浪費存儲空間。故為了節省存儲空間,UTF8 一般用1 到4 個字節表示一個字符,比如:英文字符用1 個字節,常用漢字用2 個字節。
  • Rob Pike 帶你重新認識字符串、字節、rune和字符
    然後將此處使用的格式字符串與上面的格式字符串進行比較,fmt.Printf("% x.", sample)注意字節之間留有的空格,從而使結果不那麼難以理解:bd b2 3d bc 20 e2 8c 98還有一件事。%q(帶引號) 動詞將轉義字符串中所有不可列印的字節序列,會讓輸出無歧義。