1. 程式人生 > 其它 >Effective Python 讀書筆記一

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語句應該按模組的字母
      順序來排列。

另外,作者推薦了一個Python原始碼靜態分析工具,它可以自動檢查受測程式碼是否符合PEP8風格指南,而且還能找出Python程式裡的多種常見錯誤。

第三條:瞭解bytes、str、和unicode的區別

之後主要使用Python3,在此對Python3進行討論。

本條講了Python3中有bytesstr這兩種表示字元序列的型別。前者的例項包含原始的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的語法特性(寫複雜的單行表示式),會使程式碼的可讀性變差,不如將複雜的表示式移入輔助函式中,進行多次呼叫。

第五條:瞭解切割序列的方法

切割序列也就是切片操作,通常我們可以對liststrbytes進行切片,還可以延伸到對實現了__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))

未完待續...