Effective Python 讀書筆記一
Effective Python讀書筆記一
目錄前言
《Effective Python.編寫高質量Python程式碼的59個有效方法》的作者是Brett Slatkin,這本書旨在向讀者講解如何以Pythonic(符合Python風格)的方式來編寫程式。可以作為字典一樣查閱,也可以像普通圖書一樣閱讀。
Effective一詞,並不單單侷限於執行速度層面的高效率,同時有著令程式碼易於閱讀、易於測試且易於維護等意思,此外,還蘊含這易於擴充套件,易於修改和易於多人協作等高階的概念。
我寫這個讀書筆記的想法是結合工作中使用Python的經驗,記錄59個有效方法中有重要記錄價值以及對我有較大啟發的內容。希望閱讀後在以後的工作中能夠提高Python編碼效率。同時使程式碼更加易讀、易維護、易擴充套件。
第1章 用Pythonic方式思考
什麼叫pythonic方式?其實就是Python語言的最佳程式設計習慣。
書中解釋說:對C++或Java等其他語言比較熟悉的人,可能還在按自己喜歡的風格來使用Python;而剛剛接觸Python的程式設計師,則需要逐漸熟悉許多可以用Python程式碼來表達的概念。但無論哪一種開發者,都必須知道如何以最佳方式完成常見的Python程式設計工作,這種最佳方式,就是Pythonic方式。該方式將會影響你所寫的每個程式。
第一條:確認自己所用的python版本
這條講python2和python3兩個版本一些基本的區別和未來的開發和維護方向。主要的內容是:
- python2的功能開發已經凍結,只會進行bug修復,安全增強以及移植的工作。
- python3會經常增加新功能並提供改進。並且大部分python開源原始碼庫已經支援Python3。因此作者建議使用Python3來進行新專案的開發。
第二條:遵循PEP8風格指南
《Python Enhancement Proposal#8》 (8號Python增強提案)又叫PEP8,它是針對Python程式碼格式而編訂的風格指南。儘管可以在保證語法正確的前提下隨意編寫Python程式碼,但是,採用一致的風格來書寫可以令程式碼更加易懂、更加易讀。
文中提到如下幾點,有比較記錄一下
-
空白
- 使用space(空格)來表示縮排,而不要用tab(製表符)。
- 和語法相關的每一層縮排都用4個空格來表示。
- 每行的字元數不應超過79。pycharm設定程式碼行的長度為79字元(PEP8)
- 對於佔據多行的長表示式來說,除了首行之外的其餘各行都應該在通常的縮排
級別之上再加4個空格。 - 檔案中的函式與類之間應該用兩個空行隔開。
- 在同一個類中,各方法之間應該用一個空行隔開。
- 在使用下標來獲取列表元素、呼叫函式或給關鍵字引數賦值的時候,不要在兩
旁新增空格。 - 為變數賦值的時候,賦值符號的左側和右側應該各自寫上一個空格,而且只寫
一個就好。
-
命名
- 函式、變數及屬性應該用小寫字母來拼寫,各單詞之間以下劃線相連,例如,
lowercase_ underscore。 - 受保護的例項屬性,應該以單個下劃線開頭,例如,_leading underscore。
- 私有的例項屬性,應該以兩個下劃線開頭,例如,_double_leading _underscore。
- 類與異常,應該以每個單詞首字母均大寫的形式來命名,例如,CapitalizedWord。
- 模組級別的常量,應該全部採用大寫字母來拼寫,各單詞之間以下劃線相連,
例如,ALLCAPS。 - 類中的例項方法(instance method),應該把首個引數命名為self,以表示該物件自身。
- 類方法(class method)的首個引數,應該命名為cls,以表示該類自身。
- 函式、變數及屬性應該用小寫字母來拼寫,各單詞之間以下劃線相連,例如,
-
表示式和語句
- 不要通過檢測長度的辦法(如if len(somelist)==0)來判斷somelist是否為[]
或"等空值,而是應該採用if not somelist這種寫法來判斷,它會假定:空值將
自動評估為False。 - 檢測somelist是否為[1]或'hi'等非空值時,也應如此,if somelist語句預設會把
非空的值判斷為True。 - 不要編寫單行的if語句、for迴圈、while迴圈及except複合語句,而是應該把
這些語句分成多行來書寫,以示清晰。 - import語句應該總是放在檔案開頭。
- 引入模組的時候,總是應該使用絕對名稱,而不應該根據當前模組的路徑來
使用相對名稱。例如,引人bar包中的foo模組時,應該完整地寫出from bar
import foo,而不應該簡寫為import foo。 - 如果一定要以相對名稱來編寫import語句,那就採用明確的寫法:from.import foo。
- 檔案中的那些import語句應該按順序劃分成三個部分,分別表示標準庫模組、
第三方模組以及自用模組。在每一部分之中,各import語句應該按模組的字母
順序來排列。
- 不要通過檢測長度的辦法(如if len(somelist)==0)來判斷somelist是否為[]
另外,作者推薦了一個Python原始碼靜態分析工具,它可以自動檢查受測程式碼是否符合PEP8風格指南,而且還能找出Python程式裡的多種常見錯誤。
第三條:瞭解bytes、str、和unicode的區別
之後主要使用Python3,在此對Python3進行討論。
本條講了Python3中有bytes
和str
這兩種表示字元序列的型別。前者的例項包含原始的8位值,後者的示例包含Unicode字元。這句話說的不是很清晰,可以參考淺析Python3中的bytes和str型別 - Chown-Jane-Y - 部落格園 (cnblogs.com)
關於Unicode、UTF-8等編碼方式,阮一峰有篇部落格講的比較通俗易懂:字元編碼筆記:ASCII,Unicode 和 UTF-8 - 阮一峰的網路日誌 (ruanyifeng.com)
在這一條中,作者強調要把編碼和解碼的操作放在介面最外圍來做,程式的核心部分應該使用Unicode字元型別。另外作者給了兩個helper函式,以便是字元型別在bytes和str之間進行轉換:
- 接收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
- 接收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
最後一點需要關注的是,在Python3中,如果要將資料寫入檔案或者從檔案中讀取資料,如果是對bytes進行操作,需要使用wb
以及rb
模式,否則會報TypeError。
with open('/tmp/test.bin','wb') as f:
f.write(os.urandom(10))
第四條:使用輔助函式來取代複雜的表示式
這條是講,如果開發者過度運用Python的語法特性(寫複雜的單行表示式),會使程式碼的可讀性變差,不如將複雜的表示式移入輔助函式中,進行多次呼叫。
第五條:瞭解切割序列的方法
切割序列也就是切片操作,通常我們可以對list
、str
、bytes
進行切片,還可以延伸到對實現了__getitem__
和__setitem__
這兩個內建方法的Python類進行切片。
基本語法不再討論。沒有什麼特別值得關注的點。
第六條:在單次切片操作內,不要同時指定start、end和stride
沒有特別值得關注的點。
其中講到了序列反轉的一個用法(對已經編碼成UTF-8位元組串的Unicode字元無效):
a= '123'
a[::-1]
第七條:用列表推導來取代map和filter
Python內建的map和filter我沒有用過,但是列表推導以及dict和set的列表推導式倒是經常使用。
下面記錄這幾種推導式:
# 列表推導
a = [1,2,3,4]
squares = [x**2 for x in a]
# 字典推導
fruit_dict = {banana:1,apple:2,orange:3}
rank_dict = {rank:name:for name,rank in fruit_dict.items()}
第八條:不要使用含有兩個以上的表示式的列表推導
如題,原因是為了便於程式碼閱讀和理解
第九條:用生成器表示式來改寫資料量較大的列表推導
這一條主要是為了解決一個問題:如果列表推導過程中,輸入的資料很多的話,會消耗大量記憶體,並且導致程式執行時間變慢。
典型場景——讀取檔案
value = [len(x) for x in open('/tmp/file.txt')]
解決方式是使用生成器表示式,即:
it = (len(x) for x in open('/tmp/file.txt'))
print(next(it))
# 或者 it.__next__()
生成器表示式經常用於處理列表中儲存大量資料,導致記憶體大量消耗的問題。生成器的簡單介紹見Python生成器詳解 (biancheng.net),生成器本質上是特殊的迭代器。使用生成器表示式會返回一個迭代器,而不會處理檔案內容,可以逐次產生輸出值,從而減少了記憶體的佔用。
第十條:儘量用enumerate取代range
這條的使用場景就是想要在迭代列表時,還想要拿到列表的索引,那麼就可以使用enumerate
。
作者給了一個用range
拿到列表索引的例子,有點意思,哈哈哈,就顯得很呆。
for i in range(len(flavor_list)):
flavor = flavor_list[i]
print('%d:%s' % (i + 1, flavor))
另外記錄一個我之前不知道的用法,可以在enumerate後加一個引數作為計數的起始值。
for i,flavor in enumerate(flavor_list,1)
print('%d:%s' % (i , flavor))