豆瓣评论9.5的《Effective Python》,帮你解决80%难题!
上周五晚上 11 點,我在瀏覽藍橋云課代碼交流Q時,無意看到了一條這樣的消息:“最近看到豆瓣推薦,正在看《 Effective Python :編寫高質(zhì)量 Python 代碼的 90 個有效方法》,受益匪淺!但難度好大,理解也太費勁了!”
看到這條消息,我火速趕去豆瓣,吃下這波安利。豆瓣評分 9.5 分,真不愧是一本經(jīng)典的 Pyhon 學(xué)習(xí)書籍!
但是,這本書是 Python 編程進階必備手冊,并不適合 Python 零基礎(chǔ)小白。之前,我也說過很多次, Python 入門易,進階難,這本書能讓有一定 Python 基礎(chǔ)的人更上一層。
藍橋云課推出了這本書的課程——《 Effective Python :編寫高質(zhì)量 Python 代碼的 90 個有效方法》,通過90個實驗的方式,幫助你更好地理解這本書,并且提升使用 Python 代碼的技能。(Tips:文末有超大福利禮包哦~)
01 查詢自己使用的 Python 版本
很多電腦都預(yù)裝了多個版本的標準 Cpython 運行時環(huán)境,然而,在命令行中輸入 python 命令之后,系統(tǒng)究竟會執(zhí)行哪個版本則是很難確定的。
在某些操作系統(tǒng)里面,python 通常是 python 2.7 的別名,但也有可能是 python 2.6 或 python 2.5 等舊版 python 的別名。
大家可以用 --version 標志運行 python 命令,準確找出所使用的具體 python 版本。
python --version
如果你要用的不是 python 2 而是 python 3,那么通常可以輸入 python3 命令來啟動。
python3 --version
運行結(jié)果如圖所示:
可以看出,當前實驗環(huán)境 python 的默認版本是 python 3.8.5。
也可以通過 python 內(nèi)置的 sys 模塊查詢相關(guān)的值確定當前使用的 python 版本。
import sysprint(sys.version_info)print(sys.version)
python 的核心開發(fā)者與廣大用戶都在積極地更新 python3,并不斷地改進。
實驗中會提到 python3 的許多新功能,這些功能都很強大。比較常見的 python 開源程序庫都兼容 python3,并且通常都會把重點放在 python3。
我強烈推薦大家用 python3 來開發(fā)所有的項目,這里我給大家以下 3 點注意事項,在實驗開始前一定要確認清楚:
python 3 是最新版的 python,而且受到了很好的支持,大家應(yīng)該用 python 3 開發(fā)項目;
在操作系統(tǒng)的命令行界面運行 python 時,要確認該 python 的版本是否跟你要使用的版本相同;
不要再用 python 2 做開發(fā)了,因為該版本已于 2020 年 1 月 1 日停止更新維護。
02 遵循 PEP 8 風(fēng)格指南
PEP 8 是一份針對 Python 代碼格式而編訂的風(fēng)格指南。盡管只要語法正確,代碼隨便怎么寫都行,但采用一致的風(fēng)格可以使代碼更易讀、更易懂。
如果你的代碼風(fēng)格和其他 Python 程序員的相同,那么你就能夠更加順利地與大家一起做項目。即便你的代碼只給自己看,也應(yīng)該按照這套風(fēng)格來寫,以便以后修改更加容易一些,而且能夠避開很多常見的錯誤。
PEP 8 非常詳細地描述了如何編寫清晰的 Python 代碼,而且會隨著 Python 語言的發(fā)展持續(xù)更新。所以,大家應(yīng)該把完整指南閱讀一遍(PEP 8)。
這幾條規(guī)則你絕對應(yīng)該遵循:
1.與空白有關(guān)的建議
在 Python 中,空白(whitespace)在語法上相當重要。Python 程序員對空白字符的用法尤其在意,因為它們會影響代碼的清晰程度。
用空格(space)表示縮進,而不要用制表符(tab)。
和語法相關(guān)的每一層縮進都用 4 個空格表示。
每行不超過 79 個字符。
對于占據(jù)多行的長表達式來說,除了首行之外的其余各行都應(yīng)該在通常的縮進級別之上再加 4 個空格。
在同一份文件中,函數(shù)與類之間用兩個空行隔開。
在同一個類中,方法與方法之間用一個空行隔開。
使用字典(dict)時,鍵與冒號之間不加空格,寫在同一行的冒號和值之間應(yīng)該加一個空格。
給變量賦值時,賦值符號的左邊和右邊各加一個空格,并且只加一個空格就好。
給變量的類型做注解(annotation)時,不要把變量名和冒號隔開,但在類型信息前應(yīng)該有一個空格。
2.與命名有關(guān)的建議
PEP 8 建議采用不同的方式來給 Python 代碼中的各個部分命名,這樣在閱讀代碼時, 就可以根據(jù)這些名稱看出它們在 Python 語言中的角色。
函數(shù)、變量及屬性用小寫字母來拼寫,各單詞之間用下劃線相連,例如:lowercase_underscore。
受保護的實例屬性,用一個下劃線開頭,例如:_leading_underscore 。
私有的實例屬性,用兩個下劃線開頭,例如: __double_leading_underscore。
類(包括異常)命名時,每個單詞的首字母均大寫,例如: CapitalizedWord 。
模塊級別的常量,所有字母都大寫,各單詞之間用下劃線相連,例如:ALL_CAPS 。
類中的實例方法,應(yīng)該把第一個參數(shù)命名為 self,用來表示該對象本身。
類方法的第一個參數(shù),應(yīng)該命名為cls,用來表示這個類本身。
3.與表達式和語句有關(guān)的建議
The Zen of Python 中提到:“每件事都應(yīng)該有簡單的做法,而且最好只有一種。”PEP 8 就試著運用這個理念,來規(guī)范表達式和語句的寫法。
采用行內(nèi)否定即把否定詞直接寫在要否定的內(nèi)容前面,而不要放在整個表達式的前面,例如應(yīng)該寫if a is not b,而不是 if not a is b 。
不要通過長度判斷容器或序列是不是空的,例如不要通過 if len(somelist) == 0 判斷 somelist 是否為 [] 或 ‘’ 等空值,而是應(yīng)該采用 if not somelist 這樣的寫法來判斷,因為 Python 會把空值自動評估為 False 。
如果要判斷容器或序列里面有沒有內(nèi)容(比如要判斷 somelist 是否為 [1] 或 'hi ’ 這樣非空的值),也不應(yīng)該通過長度來判斷,而是應(yīng)該采用 if somelist 語句,因為 Python 會把非空的值自動判定為 True 。
不要把 if 語句、for 循環(huán)、while 循環(huán)及 except 復(fù)合語句擠在一行。應(yīng)該把這些語句分成多行來寫,這樣更加清晰。
如果表達式一行寫不下,可以用括號將其括起來,而且要適當?shù)靥砑訐Q行與縮進,以便于閱讀。
多行的表達式,應(yīng)該用括號括起來,而不要用 \ 符號續(xù)行。
4.與引入有關(guān)的建議
import 語句(含from x import y)總是應(yīng)該放在文件開頭。
引入模塊時,總是應(yīng)該使用絕對名稱,而不應(yīng)該根據(jù)當前模塊路徑而使用相對名稱。例如,要引入 bar 包中的 foo 模塊,應(yīng)該完整地寫出 from bar import foo, 即便當前路徑為 bar 包里,也不應(yīng)該簡寫為 import foo 。
如果一定要用相對名稱來編寫 import 語句,那就應(yīng)該明確地寫成:from . import foo 。
文件中的 import 語句應(yīng)該按順序劃分成三個部分:首先引入標準庫里的模塊,然后引入第三方模塊,最后引入自己的模塊。屬于同一個部分的 import 語句按字母順序排列。
實驗總結(jié):
編寫 Python 代碼時,總是應(yīng)該遵循 PEP 8 風(fēng)格指南。
與廣大 Python 開發(fā)者采用同一套代碼風(fēng)格,可以使項目更利于多人協(xié)作。
采用一致的風(fēng)格編寫代碼,代碼的后續(xù)修改更容易。
03 了解 bytes 與 str 的區(qū)別
bytes 實例包含的是原始數(shù)據(jù),即 8 位的無符號值(通常按照 ASCII 編碼標準來顯示)。
a = b'h\x65llo'print(a)print(list(a))print(type(a))
運行效果如下:
str 實例包含的是 Unicode 碼點(code point,也叫做代碼點),這些碼點與人類語言之中的文本字符相對應(yīng)。
b = 'a\u0300 propos'print(b)print(list(b))print(type(b))
運行效果如下:
大家一定要記住:str 實例不一定非要用某一種固定的方案編碼成二進制數(shù)據(jù),bytes 實例也不一定非要按照某一種固定的方案解碼成字符串。
要把 Unicode 數(shù)據(jù)轉(zhuǎn)換成二進制數(shù)據(jù),必須調(diào)用 str 的 encode 方法。要把二進制數(shù)據(jù)轉(zhuǎn)換成 Unicode 數(shù)據(jù),必須調(diào)用 bytes 的 decode 方法。
調(diào)用這些方法的時候,可以明確指出自己要使用的編碼方案,也可以采用系統(tǒng)默認的方案,通常是指 UTF-8(但有時也不一定,下面就會講到這個問題)。
編寫 Python 程序的時候,一定要把解碼和編碼操作放在界面最外層來做,讓程序的核心部分,可以使用 Unicode 數(shù)據(jù)來運作,這種辦法通常叫做 Unicode 三明治(Unicode sandwich)。
程序的核心部分,應(yīng)該用 str 類型來表示 Unicode 數(shù)據(jù),并且不要鎖定到某種字符編碼上面。
這樣可以讓程序接受許多種文本編碼(例如Latin-1 、Shift JIS 及Big5),并把它們都轉(zhuǎn)化成 Unicode,也能保證輸出的文本信息都是用同一種標準(最好是 UTF-8)來編碼的。
兩種不同的字符類型與 Python 中兩種常見的使用情況相對應(yīng):
開發(fā)者需要操作原始的 8 位值序列,序列里面的這些 8 位值合起來表示一個應(yīng)該按 UTF-8 或其他標準編碼的字符串。
開發(fā)者需要操作通用的 Unicode 字符串,而不是操作某種特定編碼的字符串。
我們通常需要編寫兩個輔助函數(shù)(helper function),以便在這兩種情況之間轉(zhuǎn)換,確保輸入值類型符合開發(fā)者的預(yù)期形式。第一個輔助函數(shù)接受 bytes 或 str 實例,并返回 str:
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 # Instance of strprint(repr(to_str(b'foo')))print(repr(to_str('bar')))運行效果如下:
第二個輔助函數(shù)也接受 bytes 或 str 實例,但它返回的是 bytes:
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 # Instance of bytesprint(repr(to_bytes(b'foo')))print(repr(to_bytes('bar')))運行效果如下:
在 Python 中使用原始的 8 位值與 Unicode 字符串時,有兩個問題要注意:
第一個問題,bytes 與 str 這兩種類型似乎是以相同的方式工作的,但其實例并不相互兼容,所以在傳遞字符序列的時候必須考慮好其類型。
可以用 + 操作符將 bytes 添加到 bytes , str 也可以這樣。
print(b'one' + b'two')print('one' + 'two')
運行效果如下:
但是不能將 str 實例添加到 bytes 實例上面:
也不能將 bytes 實例添加到 str 實例上面:
bytes 與 bytes 之間可以用二元操作符(binary operator)來比較大小,str 與 str 之間也可以:
assert b'red' > b'blue'assert 'red' > 'blue'
但是 str 實例不能與 bytes 實例比較:
判斷 bytes 與 str 實例是否相等,總是會評估為假(False),即便這兩個實例表示的字符完全相同,它們也不相等。例如在下面這個例子里,它們表示的字符串都相當于 ASCII 編碼之中的 foo 。
兩種類型的實例都可以出現(xiàn)在 % 操作符的右側(cè),用來替換左側(cè)那個格式字符串(format string)里面的 %s 。
print(b'red %s' % b'blue')print('red %s' % 'blue')
運行效果如下:
如果格式字符串是 bytes 類型,那么不能用 str 實例來替換其中的 %s,因為 Python 不知道這個 str 應(yīng)該按照什么方案來編碼。
但反過來卻可以,也就是說如果格式字符串是 str 類型,則可以用 bytes 實例來替換其中的 %s ,問題是,這可能跟你想要的結(jié)果不一樣。
這樣做,會讓系統(tǒng)在 bytes 實例上面調(diào)用 repr 方法(參見第 75 條),然后用這次調(diào)用所得到的結(jié)果替換格式字符串里的 %s ,因此,程序會直接輸出 b'blue ',而不是像你想的那樣,輸出 blue 本身。
第二個問題,發(fā)生在操作文件句柄的時候,這里的句柄指由內(nèi)置的 open 函數(shù)返回的句柄。這樣的句柄默認需要使用 Unicode 字符串操作,而不能采用原始的 bytes。
習(xí)慣了 Python 2 的開發(fā)者,尤其容易碰到這個問題,進而導(dǎo)致程序出現(xiàn)奇怪的錯誤。例如,向文件寫入二進制數(shù)據(jù)的時候,下面這種寫法其實是錯誤的。
程序發(fā)生異常是因為在調(diào)用 open 函數(shù)時,指定的是 'w ’ 模式,所以系統(tǒng)要求必須以文本模式寫入。
如果想用二進制模式,那應(yīng)該指定 ‘wb’ 才對。在文本模式下,write 方法接受的是包含 Unicode 數(shù)據(jù)的 str 實例,不是包含二進制數(shù)據(jù)的 bytes 實例。所以,我們得把模式改成 ‘wb’ 來解決該問題。
讀取文件的時候也有類似的問題。例如,如果要把剛才寫入的二進制文件讀出來,那么不能用下面這種寫法。
程序出錯,是因為在調(diào)用 open 函數(shù)時指定的是 'r ’ 模式,所以系統(tǒng)要求必須以文本模式來讀取。
若要用二進制格式讀取,應(yīng)該指定 ‘rb’。以文本模式操縱句柄時,系統(tǒng)會采用默認的文本編碼方案處理二進制數(shù)據(jù)。所以,上面那種寫法會讓系統(tǒng)通過 bytes.decode 把這份數(shù)據(jù)解碼成 str 字符串,再用 str.encode 把字符串編碼成二進制值。
然而對于大多數(shù)系統(tǒng)來說,默認的文本編碼方案是 UTF-8,所以系統(tǒng)很可能會把 '\xf1\xf2\xf3\xf4\xf5 '當成 UTF-8 格式的字符串去解碼,于是就會出現(xiàn)上面那樣的錯誤。
為了修正錯誤,需要把模式改成 ‘rb’。
另一種改法是在調(diào)用 open 函數(shù)的時候,通過 encoding 參數(shù)明確指定編碼標準,以確保平臺特有的一些行為不會干擾代碼的運行效果。
例如,假設(shè)剛才寫到文件里的那些二進制數(shù)據(jù)表示的是一個采用 'cp1252 ’ 標準(cp1252 是一種老式的 Windows 編碼方案)來編碼的字符串,則可以這樣寫:
這樣程序就不會出現(xiàn)異常了,但返回的字符串也與讀取原始字節(jié)數(shù)據(jù)所返回的有很大區(qū)別。
通過這個例子,我們要提醒自己注意當前操作系統(tǒng)默認的編碼標準(可以執(zhí)行python3 -c 'import locale; print(locale.getpreferredencoding())' 命令查看),了解它與你所期望的是否一致。如果不確定,那就在調(diào)用 open 時明確指定 encoding 參數(shù)。
實驗總結(jié):
bytes 包含的是由 8 位值所組成的序列,str 包含的是由 Unicode 碼點所組成的序列。
我們可以編寫輔助函數(shù)來確保程序收到的字符序列確實是期望要操作的類型(要知道自己想操作的到底是 Unicode 碼點,還是原始的 8 位值。用 UTF-8 標準給字符串編碼,得到的就是這樣的一系列 8 位值)。
bytes 與 str 這兩種實例不能在某些操作符(例如 > 、== 、+ 、% 操作符)上面混用。
從文件中讀取二進制數(shù)據(jù)(或者把二進制數(shù)據(jù)寫入文件)時,應(yīng)該用 ‘rb’(‘wb’) 這樣的二進制模式打開文件。
如果要從文件中讀取(或者要寫入文件之中)的是 Unicode 數(shù)據(jù),那么必須注意系統(tǒng)默認的文本編碼方案。若無法肯定,可通過 encoding 參數(shù)明確指定。
以上內(nèi)容出自藍橋云課訓(xùn)練營《 Effective Python :編寫高質(zhì)量 Python 代碼的 90 個有效方法》,但由于內(nèi)容比較多,我就不再一一展開。
04 福利 Tips
現(xiàn)在加入藍橋云課訓(xùn)練營《 Effective Python :編寫高質(zhì)量 Python 代碼的 90 個有效方法》,可享受八折優(yōu)惠。
參考資料
超強干貨來襲 云風(fēng)專訪:近40年碼齡,通宵達旦的技術(shù)人生總結(jié)
以上是生活随笔為你收集整理的豆瓣评论9.5的《Effective Python》,帮你解决80%难题!的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 编程科普|你知道的关于 Python 的
- 下一篇: Python挑战题目,你会了吗?