編寫高效Python函數(shù)的五個技巧,真心建議你牢記,并當作此后編碼的準繩!
編寫高效和可維護的Python函數(shù)對于生成高質(zhì)量的代碼至關(guān)重要。本文將向大家分享5個關(guān)鍵技巧(也是眾多技術(shù)大牛多年以來的經(jīng)驗總結(jié)),幫助你完善Python函數(shù)并提高代碼的可讀性、可維護性和穩(wěn)健性。
以下是文章的整體概覽:
首先,強調(diào)遵循單一職責原則,確保每個函數(shù)只執(zhí)行一個任務。接下來,討論了類型提示(type hints)為增強代碼清晰度和長期可維護性帶來的好處。
接著文章探討了僅關(guān)鍵字參數(shù)(keyword-only)這一Python功能,它可以通過強制使用參數(shù)名稱來最小化出錯的幾率。另一個建議是嚴格控制參數(shù)數(shù)量,僅使用函數(shù)功能所必要的參數(shù),這樣做可以減少復雜性和潛在的 bug。
最后,文章倡導使用生成器(Generator)這種內(nèi)存高效利用的技術(shù)來返回可迭代數(shù)據(jù),而不是一次性構(gòu)造并返回整個列表。
無論你是新手還是經(jīng)驗豐富的開發(fā)者,都建議從現(xiàn)在開始應用這5個技巧,并在日常工作中不斷實踐,相信你可以在未來的編程生涯中編寫出更高效、更具可讀性和可維護性的函數(shù),從而產(chǎn)生更高質(zhì)量的代碼,從而提高你的生產(chǎn)力。
1. 你的函數(shù)應該只做一件事
“你的函數(shù)應該只做一件事”原則是整潔代碼和高效編程的核心原則。這一原則也被稱為單一職責原則(Single Responsibility Principle, SRP),它的宗旨是建議一個函數(shù)應該只有一個職責或任務。這會讓你的代碼更易于閱讀、測試、調(diào)試和維護。以下是應用此原則的一些優(yōu)點:
- 可讀性(Readability):當一個函數(shù)只做一件事時,會更容易理解,初一瞥就可以一目了然。函數(shù)名可以清楚地描述其目的(命名時盡可能做到見名知意??),實現(xiàn)也很簡單。
- 可重用性(Reusability):單一目的的函數(shù)可以在程序的不同部分或其他項目中重復使用,避免重復造輪子。
- 可測試性(Testability):編寫針對一個單一功能函數(shù)的測試更容易,這種測試也更可靠。
- 可維護性(Maintainability):如果一個函數(shù)只負責一個任務,影響該任務的需求變更將局限在該函數(shù)內(nèi),減少了代碼其他部分出現(xiàn)bug的風險。
假設你正在開發(fā)一個Python程序,處理一個數(shù)字列表,需要實現(xiàn):
- 過濾掉負數(shù)。
- 對剩余的數(shù)字進行平方運算。
- 計算平方數(shù)的總和。
def filter_negative_numbers(numbers):
"""Filters out negative numbers from the list."""
return [num for num in numbers if num >= 0]
def square_numbers(numbers):
"""Return a list of squared numbers."""
return [num ** 2 for num in numbers]
def sum_numbers(numbers):
"""Return the sum of numbers."""
return sum(numbers)
def data_processing(numbers):
"""Process the list of nubers: filter, square, and sum."""
positive_numbers = filter_negative_numbers(numbers)
squared_numbers = square_numbers(positive_numbers)
total = sum_numbers(squared_numbers)
return total
if __name__ == '__main__':
numbers = [-2, -3, 4, -1, -2, 1, 5, -3]
result = data_processing(numbers)
print(result) # Output: 42
2. 增加類型提示以提高可讀性和可維護性
類型提示(Type hints)是Python中一項非常棒的功能,允許你指定變量、函數(shù)參數(shù)和返回值的預期類型。該特征在 PEP 484 中引入,類型提示不會影響程序運行時的行為,而是提供了一種對代碼執(zhí)行靜態(tài)類型檢查的方式。它們提高了代碼的可讀性,并幫助開發(fā)人員理解預期的輸入和輸出類型,使代碼更易于維護。添加類型提示可以改善以下幾點:
- 提高可讀性:通過明確指出函數(shù)期望的參數(shù)類型和返回值類型,類型提示使代碼更具可讀性,并且更容易理解,一目了然。
- 錯誤檢測:可以使用像 mypy 這樣的工具進行靜態(tài)類型檢查,在開發(fā)過程的早期就捕捉潛在的 bug。
- 文檔:類型提示可以作為一種文檔形式,提供有關(guān)如何使用函數(shù)和方法的寶貴信息。
在單一職責原則中我們提供了一個沒有類型提示的示例,接下來我們將提供一個具有類型提示的版本,功能一樣:
from typing import List
def filter_positive_numbers(numbers: List[int]) -> List[int]:
"""Filters out negative numbers from the list."""
return [num for num in numbers if num >= 0]
def square_positive_numbers(numbers: List[int]) -> List[int]:
"""Return a list of squared numbers."""
return [num ** 2 for num in numbers]
def sum_numbers(numbers: List[int]) -> int:
"""Return the sum of numbers."""
return sum(numbers)
def data_processing(numbers: List[int]) -> int:
"""Process the list of nubers: filter, square, and sum."""
positive_numbers = filter_positive_numbers(numbers)
squared_numbers = square_positive_numbers(positive_numbers)
total = sum_numbers(squared_numbers)
return total
if __name__ == '__main__':
numbers = [-2, -3, 4, -1, -2, 1, 5, -3]
result = data_processing(numbers)
print(result) # Output: 42
比較這兩段代碼,我們可以發(fā)現(xiàn):
- 可讀性(Readability)****
沒有類型提示:閱讀函數(shù)定義時,不太清楚期望什么類型的參數(shù)或函數(shù)返回什么類型。
有類型提示:函數(shù)簽名明確指出它們使用整數(shù)列表,并返回整數(shù)列表或單個整數(shù)。
- 錯誤檢測(Error Detection)
沒有類型提示:類型相關(guān)的錯誤可能只會在運行時被捕獲,這可能導致難以追蹤的 bug。
有類型提示:像 mypy 這樣的工具可以在編譯時檢查類型,在代碼執(zhí)行之前捕捉錯誤。
3. 強制使用僅關(guān)鍵字參數(shù)以最小化出錯幾率
強制使用“僅關(guān)鍵字參數(shù)”(keyword-only)是Python中的一種技術(shù),該技術(shù)表明,調(diào)用函數(shù)時某些參數(shù)必須通過名稱指定。
這是通過在函數(shù)定義中使用特殊語法來完成的,該語法可以防止這些參數(shù)以位置方式傳遞。這種方法可以顯著提高代碼的清晰度并減少錯誤。
在Python函數(shù)中強制使用“僅關(guān)鍵字參數(shù)”可以大大增強代碼的清晰度和正確性。關(guān)鍵字參數(shù)只能使用參數(shù)名稱指定。這種強制執(zhí)行有助于:
- 防止錯誤:通過要求參數(shù)按名稱傳遞,可以減少以錯誤順序傳遞參數(shù)的風險,從而避免出現(xiàn)微不可查的 bug。
- 提高可讀性:它使函數(shù)調(diào)用更具可讀性,明確表示每個參數(shù)的含義。
- 增強靈活性:它允許你在不破壞現(xiàn)有代碼的情況下,將來可以向函數(shù)添加更多參數(shù),因為參數(shù)是顯式命名的。
- 提高清晰度:它使代碼的意圖更加清晰,因為每個參數(shù)的目的都在調(diào)用點指定。
下面是一個郵件發(fā)送的示例。send_email 函數(shù)接受一個可選的 cc 字符串:
def send_email(recipient: str, subject: str, body: str, cc: str = None):
print(f"Sending email to {recipient}...")
print(f"Subject: {subject}")
print(f"Body: {body}")
if cc:
print(f"CC: {cc}")
if __name__ == '__main__':
send_email('jackzhang@example.com', '編寫高效Python函數(shù)的5個技巧',
'本文將向大家分享5個讓你編寫高效Python函數(shù)的技巧...', 'cc@example.com')
假設您想將可選的 cc 參數(shù)設置為關(guān)鍵字參數(shù)。可以這樣做:
# Make the optional 'cc' argument keyword-only
def send_email(recipient: str, subject: str, body: str, *, cc: str = None):
print(f"Sending email to {recipient}...")
print(f"Subject: {subject}")
print(f"Body: {body}")
if cc:
print(f"CC: {cc}")
if __name__ == '__main__':
send_email('jackzhang@example.com', '編寫高效Python函數(shù)的5個技巧',
'本文將向大家分享5個讓你編寫高效Python函數(shù)的技巧...', cc='cc@example.com')
只需要在可選參數(shù) cc 前使用 * 號將前后隔開即可將其后的參數(shù)變成僅關(guān)鍵字參數(shù)。如果你想了解更多關(guān)于關(guān)鍵字參數(shù)和位置參數(shù)的細節(jié),請閱讀我的另一篇文章(Python效率秘籍:使用“*” 和“/” 讓你的函數(shù)參數(shù)看起來更整潔)。
現(xiàn)在我們再次調(diào)用函數(shù)時可選參數(shù) cc 就必須用定義時的名稱進行值傳遞:
send_email('jackzhang@example.com', '編寫高效Python函數(shù)的5個技巧',
'本文將向大家分享5個讓你編寫高效Python函數(shù)的技巧...', cc='cc@example.com')
Sending email to jackzhang@example.com...
Subject: 編寫高效Python函數(shù)的5個技巧
Body: 本文將向大家分享5個讓你編寫高效Python函數(shù)的技巧...
CC: cc@example.com
如果我們嘗試像之前一樣所有參數(shù)都采用位置參數(shù)傳遞:
send_email('jackzhang@example.com', '編寫高效Python函數(shù)的5個技巧',
'本文將向大家分享5個讓你編寫高效Python函數(shù)的技巧...', 'cc@example.com')
你將會收到下面這樣的報錯信息:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[6], line 10
7 print(f"CC: {cc}")
9 if __name__ == '__main__':
---> 10 send_email('jackzhang@example.com', '編寫高效Python函數(shù)的5個技巧',
11 '本文將向大家分享5個讓你編寫高效Python函數(shù)的技巧...', 'cc@example.com')
TypeError: send_email() takes 3 positional arguments but 4 were given
4. 你的函數(shù)應該只使用必要參數(shù)
在定義函數(shù)時,嚴格限制參數(shù)數(shù)量,將其限制為函數(shù)操作所必要的參數(shù)至關(guān)重要。不必要的參數(shù)會常常會導致混淆,使函數(shù)調(diào)用臃腫,并且會讓維護變得更復雜。以下是你應該只使用必要參數(shù)的幾個原因:
- 提高可讀性:更少的參數(shù)使函數(shù)簽名更簡單、更易于理解。
- 增強可維護性:參數(shù)較少的函數(shù)更易于重構(gòu)和測試。
- 減少錯誤:當函數(shù)只接受必要的參數(shù)時,可以降低傳遞不正確或冗余數(shù)據(jù)的可能性。
以下是一個冗余參數(shù)的函數(shù)示例:
# example function for processing an order including unnecessary arguments
def process_order(order_id: int, customer_id: int, customer_name: str,
amount: float, discount: float = 0.0):
print(f"Processing order {order_id} for customer {customer_id} - {customer_name}")
total_amount = amount * (1 - discount)
print(f"Total amount after discount: {total_amount}")
if __name__ == '__main__':
process_order(666, 888, 'Jack Zhang', 1000.0, 0.05)
Processing order 666 for customer 888 - Jack Zhang
Total amount after discount: 950.0
在這個例子中,函數(shù) process_order 同時接受 customer_id 和 customer_name 參數(shù),如果所有必需的信息都可以從 order_id 中獲得,這兩個參數(shù)可能是不必要的。
現(xiàn)在,讓我們只使用必要的參數(shù),重構(gòu)該函數(shù):
# example function for processing an order with only necessary arguments
def process_order(order_id: int, amount: float, discount: float = 0.0):
print(f"Processing order {order_id}")
total_amount = amount * (1 - discount)
print(f"Total amount after discount: {total_amount}")
if __name__ == '__main__':
process_order(666, 1000.0, 0.05)
5. 使用生成器返回列表
生成器(Generator)是Python中一種特殊的可迭代對象,它允許你逐個迭代序列值,而不需要一次性將整個序列存儲在內(nèi)存中。生成器通過函數(shù)和 yield 關(guān)鍵字進行定義,該關(guān)鍵字允許函數(shù)返回一個值并暫停其狀態(tài),在下次請求值時恢復。這使得生成器成為處理大型數(shù)據(jù)集或數(shù)據(jù)流的高效方式。
以下是你應該使用生成器的幾個原因:
- 內(nèi)存效率:生成器一次只生成一個數(shù)據(jù)項,并且是按需生成,這意味著它們不需要將整個序列存儲在內(nèi)存中。這對于大型數(shù)據(jù)集特別有用。
- 性能:由于生成器按需生成數(shù)據(jù)項,因此它們可以讓程序性能得到提升,因為避免了創(chuàng)建和存儲大型數(shù)據(jù)結(jié)構(gòu)的開銷。
- 惰性求值:生成器按需計算值,這可以得到更高效和響應性更強的程序。
- 更簡單的代碼:生成器可以簡化創(chuàng)建迭代器所需的代碼,使其更易于閱讀和維護。
以下是一個不使用生成器返回列表的函數(shù)示例:
from typing import List
def get_squares(n: int) -> List[int]:
"""Return a list of squares from 0 to n-1."""
squares = []
for i in range(n):
squares.append(i * i)
return squares
if __name__ == '__main__':
squares_list = get_squares(10)
print(squares_list)
# Output: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
在這個例子中,函數(shù) get_squares 在返回之前會生成并存儲所有數(shù)值(0~n-1)的平方數(shù)。現(xiàn)在,我們改用生成器實現(xiàn)相同的功能:
from typing import List
def get_squares(n: int) -> List[int]:
"""Yield squares from 0 to n-1."""
for i in range(n):
yield i * i
if __name__ == '__main__':
squares_gen = get_squares(10)
print(list(squares_gen))
在Python中使用生成器返回列表可以在內(nèi)存效率、性能和惰性求值方面帶來顯著優(yōu)勢。通過按需生成值,你可以更高效地處理大型數(shù)據(jù)集,編寫更簡潔、更易維護的代碼。
兩種方法的比較清楚地顯示了使用生成器的好處,特別是對于大型或計算密集型序列的優(yōu)勢尤其顯著。