如何寫出高效Python的程式碼

巴扎黑
發布: 2017-09-09 11:38:38
原創
1364 人瀏覽過

這篇文章主要介紹了分享一下如何編寫高效且優雅的Python 程式碼,需要的朋友可以參考下

本文部分提煉自書籍:《Effective Python》&《Python3 Cookbook》,但也做出了修改,並加上了作者自己的理解和運用中的最佳實踐。

全文約 9956 字,讀完可能需要 24 分鐘。

Pythonic清單切割

list[start:end:step]

如果從清單開頭開始切割,​​那麼忽略start 位元的0,例如list[:4]

如果要切到清單尾部,則忽略end 位元的0,例如list[3:]

切割列表時,即便start 或end 索引跨界也不會有問題

列表切片不會改變原始列表。當索引都留空時,會產生一份原始列表的拷貝

列表推導式

使用列表推導式取代mapfilter

不要使用含有兩個以上表達式的列表推導式

資料多時,列表推導式可能會消耗大量內存,此時建議使用生成器表達式

#迭代

需要取得index 時使用 enumerate

enumerate可以接受第二個參數,當迭代時加在index上的數值

zip同時遍歷兩個迭代器

#zip 傳回一個元組

關於forwhile循環後的else區塊

迴圈正常結束之後會呼叫else內的程式碼

循環裡透過break跳出循環,則不會執行else

#要遍歷的序列為空時,立即執行else

#反向迭代

對於普通的序列(列表) ,我們可以透過內建的reversed()函數進行反向迭代:

除此之外,還可以透過實作類別裡的 __reversed__方法,將類別進行反向迭代:

#try/except/else/finally

如果try內沒有發生異常,則呼叫else內的程式碼

else會在finally之前執行

最終一定會執行finally,可以在其中進行清理工作

函數使用裝飾器

裝飾器用於在不改變原始函數程式碼的情況下修改已存在的函數。常見場景是增加一句調試,或為已有的函數增加log監控

舉個栗子:

##除此以外,還可以寫接收參數的裝飾器,其實就是在原本的裝飾器上的外層又嵌套了一個函數:

但是像上面那樣使用裝飾器的話有一個問題:

也就是說原始函數已經被裝飾器裡的

new_fun函數取代掉了。呼叫經過裝飾的函數,相當於呼叫一個新函數。查看原函數的參數、註解、甚至函數名稱的時候,只能看到裝飾器的相關資訊。為了解決這個問題,我們可以使用

Python 自帶的

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__

預設情況下,Python 用一個字典來保存一個物件的實例屬性。這使得我們可以在運行的時候動態的給類的實例添加新的屬性:

#然而這個字典浪費了多餘的空間-— 很多時候我們不會創建那麼多的屬性。因此透過

__slots__可以告訴 Python

不要使用字典而是固定集合來分配空間。

__call__

透過定義類別中的

__call__方法,可以使該類別的實例能夠像普通函數一樣呼叫。

透過這種方式實現的好處是,可以透過類別的屬性來保存狀態,而不必建立一個閉包或全域變數。

@classmethod & @staticmethod

#@classmethod@staticmethod很像,但他們的使用場景並不一樣。

類別內部普通的方法,都是以

self作為第一個參數,代表透過實例呼叫時,將實例的作用域傳入方法內;

#@classmethodcls作為第一個參數,代表將類別本身的作用域傳入。無論透過類別來調用,或是透過類別的實例調用,預設傳入的第一個參數都將是類別本身

@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

語句呼叫之前暫存的###__exit__###方法#########__exit__###方法關閉了檔案######要注意的是,在## #__exit__###方法內,我們關閉了文件,但最後返回###True###,所以錯誤不會被###with###語句拋出。否則###with###語句會拋出一個對應的錯誤。 ###

以上是如何寫出高效Python的程式碼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板