代码整洁之道-编写 Pythonic 代码
很多新手在開始學一門新的語言的時候,往往會忽視一些不應該忽視的細節,比如變量命名和函數命名以及注釋等一些內容的規范性,久而久之養成了一種習慣。對此呢,我特意收集了一些適合所有學習 Python 的人,代碼整潔之道。
寫出 Pythonic 代碼
談到規范首先想到就是 Python 有名的 PEP8 代碼規范文檔,它定義了編寫Pythonic代碼的最佳實踐。可以在 https://www.python.org/dev/peps/pep-0008/ 上查看。但是真正去仔細研究學習這些規范的朋友并不是很多,對此呢這篇文章摘選一些比較常用的代碼整潔和規范的技巧和方法,下面讓我們一起來學習吧!
命名
所有的編程語言都有變量、函數、類等的命名約定,以美之稱的 Python 當然更建議使用命名約定。接下來就針對類、函數、方法等等內容進行學習。
變量和函數
使用小寫字母命名函數和變量,并用下劃線分隔單詞,提高代碼可讀性。
變量的聲明
names?=?"Python"?#變量名? namejob_title?=?"Software?Engineer"?#帶有下劃線的變量名 populated_countries_list?=?[]??#帶有下劃線的變量名?還應該考慮在代碼中使用非 Python 內置方法名,如果使用 Python 中內置方法名請使用一個或兩個下劃線()。
_books?=?{}#?變量名私有化 __dict?=?[]#?防止python內置庫中的名稱混淆那如何選擇是用_還是__呢?
如果不希望外部類訪問該變量,應該使用一個下劃線(_)作為類的內部變量的前綴。如果要定義的私有變量名稱是 Python 中的關鍵字如 dict 就要使用(__)。
函數的聲明
def?get_data():?pass def?calculate_tax_data():pass函數的聲明和變量一樣也是通過小寫字母和單下劃線進行連接。
當然對于函數私有化也是和聲明變量類似。
函數的開頭使用單下劃線,將其進行私有化。對于使用 Pyton 中的關鍵字來進行命名的函數
要使用雙下劃線。
除了遵循這些命名規則之外,使用清晰易懂的變量名和很重要。
函數名規范
#?Wrong?Way def?get_user_info(id):db?=?get_db_connection()user?=?execute_query_for_user(id)return?user#?Right?way def?get_user_by(user_id):db?=?get_db_connection()user?=?execute_user_query(user_id)?return?user這里,第二個函數 get_user_by 確保使用相同的參數來傳遞變量,從而為函數提供正確的上下文。第一個函數 get_user_info 就不怎么不明確了,因為參數 id 意味著什么這里我們不能確定,它是用戶 ID,還是用戶付款ID或任何其他 ID?這種代碼可能會對使用你的API的其他開發人員造成混淆。為了解決這個問題,我在第二個函數中更改了兩個東西; 我更改了函數名稱以及傳遞的參數名稱,這使代碼可讀性更高。
作為開發人員,你有責任在命名變量和函數時仔細考慮,要寫讓人能夠清晰易懂的代碼。
當然也方便自己以后去維護。
類的命名規范
類的名稱應該像大多數其他語言一樣使用駝峰大小寫。
class?UserInformation:def?get_user(id):db?=?get_db_connection()user?=?execute_query_for_user(id)return?user常量的命名規范
通常應該用大寫字母定義常量名稱。
TOTAL?=?56 TIMOUT?=?6 MAX_OVERFLOW?=?7函數和方法的參數
函數和方法的參數命名應遵循與變量和方法名稱相同的規則。因為類方法將self作為第一個關鍵字參數。所以在函數中就不要使用 self 作為關鍵字作為參數,以免造成混淆 .
def?calculate_tax(amount,?yearly_tax):passsclass?Player:def?get_total_score(self,?player_name):pass關于命名大概就強調這些,下面讓我們看看表達式和語句中需要的問題。
代碼中的表達式和語句
users?=?[{"first_name":"Helen",?"age":39},{"first_name":"Buck",?"age":10},{"first_name":"anni",?"age":9} ] users?=?sorted(users,?key=lambda?user:?user["first_name"].lower())這段代碼有什么問題?
乍一看并不容易理解這段代碼,尤其是對于新開發人員來說,因為 lambdas 的語法很古怪,所以不容易理解。雖然這里使用 lambda 可以節省行,然而,這并不能保證代碼的正確性和可讀性。同時這段代碼無法解決字典缺少鍵出現異常的問題。
讓我們使用函數重寫此代碼,使代碼更具可讀性和正確性; 該函數將判斷異常情況,編寫起來要簡單得多。
users?=?[{"first_name":"Helen",?"age":39},{"first_name":"Buck",?"age":10},{"name":"anni",?"age":9} ] def?get_user_name(users):"""Get?name?of?the?user?in?lower?case"""return?users["first_name"].lower() def?get_sorted_dictionary(users):"""Sort?the?nested?dictionary"""if?not?isinstance(users,?dict):raise?ValueError("Not?a?correct?dictionary")if?not?len(users):raise?ValueError("Empty?dictionary")users_by_name?=?sorted(users,?key=get_user_name)return?users_by_name如你所見,此代碼檢查了所有可能的意外值,并且比起以前的單行代碼更具可讀性。單行代碼雖然看起來很酷節省了行,但是會給代碼添加很多復雜性。但是這并不意味著單行代碼就不好 這里提出的一點是,如果你的單行代碼使代碼變得更難閱讀,那么就請避免使用它,記住寫代碼不是為了炫酷的,尤其在項目組中。
讓我們再考慮一個例子,你試圖讀取 CSV 文件并計算 CSV 文件處理的行數。下面的代碼展示使代碼可讀的重要性,以及命名如何在使代碼可讀中發揮重要作用。
import?csv with?open("employee.csv",?mode="r")?as?csv_file:csv_reader?=?csv.DictReader(csv_file)line_count?=?0for?row?in?csv_reader:if?line_count?==?0:print(f'Column?names?are?{",?".join(row)}')line_count?+=?1print(f'\t{row["name"]}?salary:?{row["salary"]}'f'and?was?born?in?{row["birthday?month"]}.')line_count?+=?1print(f'Processed?{line_count}?lines.')將代碼分解為函數有助于使復雜的代碼變的易于閱讀和調試。
這里的代碼在 with 語句中執行多項操作。為了提高可讀性,您可以將帶有 process salary 的代碼從 CSV 文件中提取到另一個函數中,以降低出錯的可能性。
代碼是不是變得容易理解了不少呢。
在這里,創建了一個幫助函數,而不是在with語句中編寫所有內容。這使讀者清楚地了解了函數的實際作用。如果想處理一個特定的異常或者想從CSV文件中讀取更多的數據,可以進一步分解這個函數,以遵循單一職責原則,一個函數一做一件事。這個很重要
return語句的類型盡量一致
如果希望函數返回一個值,請確保該函數的所有執行路徑都返回該值。但是,如果期望函數只是在不返回值的情況下執行操作,則 Python 會隱式返回 None 作為函數的默認值。
先看一個錯誤示范
正確的示范應該是下面這樣
def?calculate_interest(principle,?time?rate):????if?principle?>?0:return?(principle?*?time?*?rate)?/?100else:return?None def?calculate_interest(principle,?time?rate):????if?principle?<?0:return?None????return?(principle?*?time?*?rate)?/?100ChaPTER?1??PyThonIC?ThInkIng還是那句話寫易讀的代碼,代碼多寫點沒關系,可讀性很重要。
使用 isinstance() 方法而不是 type() 進行比較
當比較兩個對象類型時,請考慮使用 isinstance() 而不是 type,因為 isinstance() 判斷一個對象是否為另一個對象的子類是 true。考慮這樣一個場景:如果傳遞的數據結構是dict 的子類,比如 orderdict。type() 對于特定類型的數據結構將失敗;然而,isinstance() 可以將其識別出它是 dict 的子類。
錯誤示范
user_ages?=?{"Larry":?35,?"Jon":?89,?"Imli":?12} type(user_ages)?==?dict:正確選擇
user_ages?=?{"Larry":?35,?"Jon":?89,?"Imli":?12} if?isinstance(user_ages,?dict):比較布爾值
在Python中有多種方法可以比較布爾值。
錯誤示范
正確示范
is_empty?=?False if?is_empty使用文檔字符串
Docstrings可以在 Python 中聲明代碼的功能的。通常在方法,類和模塊的開頭使用。docstring是該對象的__doc__特殊屬性。
Python 官方語言建議使用“”三重雙引號“”來編寫文檔字符串。你可以在 PEP8 官方文檔中找到這些實踐。下面讓我們簡要介紹一下在 Python 代碼中編寫 docstrings 的一些最佳實踐 。
方法中使用docstring
def?get_prime_number():"""Get?list?of?prime?numbers?between?1?to?100.""""關于docstring的格式的寫法,目前存在多種風格,但是這幾種風格都有一些統一的標準。
即使字符串符合一行,也會使用三重引號。當你想要擴展時,這種注釋非常有用。‘
三重引號中的字符串前后不應有任何空行
使用句點(.)結束docstring中的語句
類似地,可以應用 Python 多行 docstring 規則來編寫多行 docstring。在多行上編寫文檔字符串是用更具描述性的方式記錄代碼的一種方法。你可以利用 Python 多行文檔字符串在 Python 代碼中編寫描述性文檔字符串,而不是在每一行上編寫注釋。
多行的docstring
def?call_weather_api(url,?location):"""Get?the?weather?of?specific?location.Calling?weather?api?to?check?for?weather?by?using?weather?api?and?location.?Make?sure?you?provide?city?name?only,?country?and?county?names?won't?be?accepted?and?will?throw?exception?if?not?found?the?city?name.:param?url:URL?of?the?api?to?get?weather.:type?url:?str:param?location:Location?of?the?city?to?get?the?weather.:type?location:?str:return:?Give?the?weather?information?of?given?location.:rtype:?str"""說一下上面代碼的注意點
第一行是函數或類的簡要描述
每一行語句的末尾有一個句號
文檔字符串中的簡要描述和摘要之間有一行空白
如果使用 Python3.6 可以使用類型注解對上面的docstring以及參數的聲明進行修改。
def?call_weather_api(url:?str,?location:?str)?->?str:"""Get?the?weather?of?specific?location.Calling?weather?api?to?check?for?weather?by?using?weather?api?and?location.?Make?sure?you?provide?city?name?only,?country?and?county?names?won't?be?accepted?and?will?throw?exception?if?not?found?the?city?name."""怎么樣是不是簡潔了不少,如果使用 Python 代碼中的類型注解,則不需要再編寫參數信息。
關于類型注解(type hint)的具體用法可以參考我之前寫的python類型檢測最終指南--Typing的使用
模塊級別的docstring
一般在文件的頂部放置一個模塊級的 docstring 來簡要描述模塊的使用。
這些注釋應該放在在導包之前,模塊文檔字符串應該表明模塊的使用方法和功能。
如果覺得在使用模塊之前客戶端需要明確地知道方法或類,你還可以簡要地指定特定方法或類。
在為模塊編寫文檔字符串時,應考慮執行以下操作:
對當前模塊寫一個簡要的說明
如果想指定某些對讀者有用的模塊,如上面的代碼,還可以添加異常信息,但是注意不要太詳細。
將模塊的docstring看作是提供關于模塊的描述性信息的一種方法,而不需要詳細討論每個函數或類具體操作方法。
類級別的docstring
類docstring主要用于簡要描述類的使用及其總體目標。讓我們看一些示例,看看如何編寫類文檔字符串
單行類docstring
class?Student:"""This?class?handle?actions?performed?by?a?student."""def?__init__(self):pass這個類有一個一行的 docstring,它簡要地討論了學生類。如前所述,遵守了所以一行docstring 的編碼規范。
多行類docstring
class?Student:"""Student?class?information.This?class?handle?actions?performed?by?a?student.This?class?provides?information?about?student?full?name,?age,?roll-number?and?other?information.Usage:import?studentstudent?=?student.Student()student.get_name()>>>?678998"""def?__init__(self):pass這個類 docstring 是多行的; 我們寫了很多關于 Student 類的用法以及如何使用它。
函數的docstring
函數文檔字符串可以寫在函數之后,也可以寫在函數的頂部。
def?is_prime_number(number):"""Check?for?prime?number.Check?the?given?number?is?prime?number?or?not?by?checking?against?all?the?numbers?less?the?square?root?of?given?number.:param?number:Given?number?to?check?for?prime:type?number:?int:return:?True?if?number?is?prime?otherwise?False.:rtype:?boolean"""如果我們使用類型注解對其進一步優化。
def?is_prime_number(number:?int)->bool:"""Check?for?prime?number.Check?the?given?number?is?prime?number?or?not?by?checking?against?all?the?numbers?less?the?square?root?of?given?number."""結語
當然關于 Python 中的規范還有很多很多,建議大家參考 Python 之禪和 Pep8 對代碼進行優化,養成編寫 Pythonic 代碼的良好習慣。
推薦閱讀:
終于來了!Python 編輯神器 Jupyter ,推出首款官方可視化 Debug 工具!
關注公眾號,一起學習Python
總結
以上是生活随笔為你收集整理的代码整洁之道-编写 Pythonic 代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ActiveMQ源码解析 建立连接
- 下一篇: ThreeJS阴影