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


在微信公衆號「 極客起源 」中輸入 595586 ,可學習所有的《 Python高效編程之88條軍規 》系列文章。

用編程語言寫代碼是自由的,編譯器不會強制你使用特定的格式編寫程序(只要符合語法,編譯器才無論你呢!)。因此不少程序員就會將Python當作本身熟悉的Java、C++等語言來用。不過這些編碼方式真的是最好的選擇嗎?本系列文章將爲你揭祕88種在編寫Python代碼中的規則,這些規則將會讓你Python程序更加健壯,運行效率更高。
軍規1:遵循PEP 8樣式指南
Python的PEP 8是Python官方提供了關於如何格式化Python代碼的樣式指南。儘管能夠用任何有效的方式編寫Python代碼,可是,使用一致的樣式會使你的代碼更易於訪問和閱讀,以及與其餘Python程序員使用同一種樣式有助於項目上的分工協做。即便你是惟一會閱讀代碼的人,遵循樣式指南也可讓你的代碼更容易維護,並能夠避免許多常見錯誤。
關於PEP 8的詳細內容,讀者能夠查看下面的頁面:
https://www.python.org/dev/peps/pep-0008/
下面挑出PEP 8中一些常見的應該注意的地方:
空格
在Python語言中,空格是有實際意義的。Python程序員應該更關注空格的用法,下面是與空格相關的一些建議(並不必定要遵照,但按照這個規範,會讓你的Python程序看着更舒服):
(1)使用空格代替Tab進行縮進;
(2)儘管縮進可使用任意多個空格,但建議統一使用4個空格進行縮進;
(3)每行不該該有過多的字符,建議最多不要超過79的字符;
(4)若是每行的字符過多(超過79個),應該折到下一行,並且應該在當前縮進的基礎上再使用4個空格進行縮進,以下圖所示:

(5)在文件中,若是函數和類相鄰,建議使用兩個空行將他們分開,這樣會讓代碼一目瞭然;
(6)在類中,相鄰的方法之間應該用一個空行分隔;
(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個空格;
導入模塊:
下面是PEP8關於導入模塊的一些建議:
(1)將import語句(包括from x import y和import x語句)放在文件的最頂端;
(2)若是在import語句中涉及到模塊名,應該使用絕對的模塊名,而不要使用相對的模塊名。例如,爲了從bar包導入foo模塊,應該使用from bar import foo,而不要使用Import foo;
(3)若是必需要使用相對的模塊名,應該顯式使用from . import foo形式;
(4)導入模塊應該按下面的順序:
        a. 標準的模塊
        b. 第三方的模塊
        c. 本身編寫的模塊
        並且每個子部分在導入時應該按字母順序排列;

軍規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字符串;
下面給出兩個函數來完成這些情形下的轉換:
第1個顏色將字節序列或字符串轉換一個字符串:
def to_str(bytes_or_str): if isinstance(bytes_or_str, bytes): # 將使用utf-8編碼的字節序列轉換爲字符串 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')))
運行這段代碼,會輸出以下的結果:
'hello''world'

第2個函數用於將字節序列或字符串轉換爲字節序列:
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')))
運行這段代碼,會輸出以下的結果:
b'hello'b'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')
執行代碼,會輸出以下的結果:
FalseTrue
與加號相似,字符串與字節序列不能直接比較,以下面的代碼會拋出異常:
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')
執行這行代碼,會返回以下的結果:
False
百分號(%)用於分別格式化字符串和字節序列,
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):


關注  極客起源  公衆號,得到更多免費技術視頻和文章。




本文分享自微信公衆號 - 極客起源(geekculture)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。javascript

相關文章
相關標籤/搜索