這篇文章主要介紹了分享一下如何編寫高效且優雅的Python 程式碼,需要的朋友可以參考下
本文部分提煉自書籍:《Effective Python》&《Python3 Cookbook》,但也做出了修改,並加上了作者自己的理解和運用中的最佳實踐。
全文約 9956 字,讀完可能需要 24 分鐘。
Pythonic清單切割
list[start:end:step]
如果從清單開頭開始切割,那麼忽略start 位元的0,例如list[:4]
如果要切到清單尾部,則忽略end 位元的0,例如list[3:]
切割列表時,即便start 或end 索引跨界也不會有問題
列表切片不會改變原始列表。當索引都留空時,會產生一份原始列表的拷貝
列表推導式
使用列表推導式取代map
和filter
不要使用含有兩個以上表達式的列表推導式
資料多時,列表推導式可能會消耗大量內存,此時建議使用生成器表達式
#迭代
需要取得index 時使用 enumerate
enumerate
可以接受第二個參數,當迭代時加在index
上的數值
用zip
同時遍歷兩個迭代器
#zip
傳回一個元組
關於for
和while
循環後的else
區塊
迴圈正常結束之後會呼叫else
內的程式碼
循環裡透過break
跳出循環,則不會執行else
#要遍歷的序列為空時,立即執行else
#反向迭代
對於普通的序列(列表) ,我們可以透過內建的reversed()
函數進行反向迭代:
除此之外,還可以透過實作類別裡的 __reversed__
方法,將類別進行反向迭代:
#try/except/else/finally
如果try
內沒有發生異常,則呼叫else
內的程式碼
else
會在finally
之前執行
最終一定會執行finally
,可以在其中進行清理工作
函數使用裝飾器
裝飾器用於在不改變原始函數程式碼的情況下修改已存在的函數。常見場景是增加一句調試,或為已有的函數增加log
監控
舉個栗子:
##除此以外,還可以寫接收參數的裝飾器,其實就是在原本的裝飾器上的外層又嵌套了一個函數: 但是像上面那樣使用裝飾器的話有一個問題: 也就是說原始函數已經被裝飾器裡的new_fun函數取代掉了。呼叫經過裝飾的函數,相當於呼叫一個新函數。查看原函數的參數、註解、甚至函數名稱的時候,只能看到裝飾器的相關資訊。為了解決這個問題,我們可以使用
functools.wraps方法。
functools.wraps是個很 hack 的方法,它本事作為一個裝飾器,做用在裝飾器內部將要傳回的函數上。也就是說,它是裝飾器的裝飾器,並且以原函數為參數,作用是保留原函數的各種信息,使得我們之後查看被裝飾了的原函數的信息時,可以保持跟原函數一模一樣。
此外,有時候我們的裝飾器裡可能會乾不只一個事情,此時應該把事件作為額外的函數分離出去。但又因為它可能只是和該裝飾器有關,所以此時可以建構一個裝飾器類別。原理很簡單,主要就是寫類別裡的__call__
方法,讓類別能夠像函數一樣的呼叫。
使用生成器
考慮使用生成器來改寫直接傳回清單的函數
用這個方法有幾個小問題:
每次取得到符合條件的結果,都要呼叫append
方法。但實際上我們的關注點根本不在這個方法,它只是我們達成目的的手段,實際上只需要index
就好了
返回的result
可以繼續優化
資料都存在result
裡面,如果資料量很大的話,會比較佔用記憶體
因此,使用產生器generator
會更好。生成器是使用yield
表達式的函數,呼叫生成器時,它不會真的執行,而是傳回一個迭代器,每次在迭代器上呼叫內建的next
函數時,迭代器會把生成器推進到下一個yield
表達式:
#取得到一個生成器以後,可以正常的遍歷它:
如果你還是需要一個列表,那麼可以將函數的呼叫結果當作參數,再呼叫list
方法
#可迭代物件
需要注意的是,普通的迭代器只能迭代一輪,一輪之後重複呼叫是無效的。解決這種問題的方法是,你可以定義一個可迭代的容器類別:
#這樣的話,將類別的實例迭代重複多少次都沒問題:
但要注意的是,只是實作__iter__
方法的迭代器,只能透過for
循環來迭代;想要透過next
方法迭代的話則需要使用iter
方法:
有時候,方法接收的參數數目可能不一定,例如定義一個求和的方法,至少要接收兩個參數: 對於這種接收參數數目不一定,而且不在乎參數傳入順序的函數,則應該利用位置參數
*args:
args傳遞給函數時,需要先轉換成元組
tuple。這意味著,如果你將一個生成器作為參數帶入函數中,生成器將會先遍歷一遍,轉換為元組。這可能會消耗大量記憶體:
定義只能使用關鍵字參數的函數
普通的方式,在調用時不會強制要求使用關鍵字參數 使用Python3 中強制關鍵字參數的方式
使用Python2 中強制關鍵字參數的方式
#關於參數的預設值算是老生常談了:函數的預設值只會在程式載入模組並讀取到該函數的定義時設定一次
#也就是說,如果給某參數賦予動態的值(例如[]或
{}),則如果之後在呼叫函數的時候給了參數其他參數,則以後再呼叫這個函數的時候,之前定義的預設值將會改變,成為上一次呼叫時賦予的值:
因此,更建議使用None
作為預設參數,在函數內進行判斷之後賦值:
__slots__
__slots__可以告訴 Python
__call__
__call__方法,可以使該類別的實例能夠像普通函數一樣呼叫。
@classmethod &
@staticmethod
#@classmethod和
@staticmethod很像,但他們的使用場景並不一樣。
self作為第一個參數,代表透過實例呼叫時,將實例的作用域傳入方法內;
#@classmethod以
cls作為第一個參數,代表將類別本身的作用域傳入。無論透過類別來調用,或是透過類別的實例調用,預設傳入的第一個參數都將是類別本身
@staticmethod不需要傳入預設參數,類似於一個普通的函數
Date的類,用於儲存年/月/日三個資料
Date類,該類別會在初始化時設定
day/month/year屬性,並且透過
property設定了一個
getter,可以在實例化之後,透過
time取得儲存的時間:
2016-11-09這樣的字串來建立一個
Date實例?
Date類別有關
@classmethod,在類別的內部新建一個格式化字串,並傳回類別的實例的方法:
Date類別來呼叫
from_string方法建立實例,並且不會侵略、修改舊的實例化方式:
@classmethod內,可以透過
cls參數,取得到跟外部呼叫類別時一樣的便利
@staticmethod,因為本身類似於普通的函數,所以可以把和這個類別相關的helper
@staticmethod,放在類別裡,然後直接透過類別來呼叫這個方法。
@staticmethod方法放在
Date類別內後,可以透過類別來呼叫這些方法:
with語句常伴隨上下文管理器一起出現,經典場景有:
透過with
語句,程式碼完成了檔案開啟操作,並在呼叫結束,或讀取發生異常時自動關閉文件,即完成了檔案讀寫之後的處理工作。如果不通過上下文管理器的話,則會是這樣的程式碼:
比較繁瑣吧?所以說使用上下文管理器的好處就是,透過呼叫我們預先設定好的回調,自動幫我們處理程式碼區塊開始執行和執行完畢時的工作。而透過自訂類別的__enter__
和__exit__
方法,我們可以自訂一個上下文管理器。
然後可以以這樣的方式進行呼叫:
##with
ReadFile類別的
__exit__
然後呼叫
ReadFile類別的
__enter__
__enter__
方法開啟文件,並將結果傳回給
with
上一步驟的結果傳遞給
file_read
在
with語句內對
file_read參數進行操作,讀取每一行
讀取完成之後,
with
以上是如何寫出高效Python的程式碼的詳細內容。更多資訊請關注PHP中文網其他相關文章!