Effective Python 學習筆記 4
盡量使用異常來表示特殊情況,而不要返回None
_表示用不到的變量
要點
- 用None這個返回值來表示特殊意義的函數,很容易使調用者犯錯,以為None和0及空字符串之類的值,在表達式里面都會評估為False
- 函數遇到特特殊情況時應該拋出異常,而不是返回None
了解如何在閉包里使用外圍作用域中的變量
'''
eg. 有一份列表,其中元素都是數字,現在對其排序,要把出現在某個群組內的數字,放在群組外的那些數字之前。
'''
def sort_pri(values, group):
def helper(x):
if x in group:
return (0, x)
return (1, x)
values.sort(key = helper)
'''
上述函數成立的原因:
1. 函數是一等對象(first-class object)
2. python支持閉包
3. python中使用特殊的規則來比較兩個元組。它首先比較各元組中下標為0的對應元素,如果相等,再比較下標為1的元素,如果還是想等,就繼續依次比較。
'''
numbers = [8, 3, 1, 2, 5, 4, 7, 6]
group = {2, 3, 5, 7}
sort_pri(numbers, group)
print(numbers)
>>> [2, 3, 5, 7, 1, 4, 6, 8]
要點
- 對于定義在某作用域內的閉包來說,它可以引用這些作用域中的變量
- 使用默認方式對閉包內的變量賦值,不會影響外圍作用域的同名變量
- 在python 3中,程序可以在閉包內用nonlocal語句來修飾某個名稱,使該閉包能夠修改外圍作用域中的同名變量
- 除了簡單的函數,盡量不要使用nonlocal語句
考慮使用生成器來改寫直接返回列表的函數
# eg.返回字符串中英文單詞的首字母和其下標
def index_words(text):
result = []
if text:
result.append(0)
for index, letter in enumerate(text):
if letter == ' ':
result.append(index + 1)
return result
words = 'I am python'
re = index_words(words)
print(re)
>>> [0, 2, 5]
以上程序的問題:
- 代碼擁擠,每次找到新的結果,都要調用append方法。而我們真正強調的不是對append的調用,而是該方法給列表中添加的那個值且函數首位都要對resut進行創建和返回
# 生成器改寫
def index)words_iter(text):
if text:
yield 0
for index, letter in enumerate(text):
if letter == ' ':
yield index + 1
### ···
re = list(index_words_iter(address))
- index_words函數在它返回前,要把所有結果都放在列表中。如果數據量非常大,那么程序可能會耗盡內存。用生成器改寫后,可以應對任意長度的輸入數據
要點
- 使用時生成器比用list返回結果更加清晰
- 由生成器函數所返回的那個迭代器,可以把生成器函數體中,傳給yield表達式的那些值,逐次生產出來
- 無論數據量多大,生成器都能產生一系列輸出,不會對內存造成壓力
在參數上面迭代時要多加小心
細節見書本第17條
要點
- 如果參數是迭代器,那么可能會導致奇怪的行為并錯失某些值
- python的迭代器協議,描述了容器和迭代器應該如何與iter和next內置函數、for循環及相關表達式相互配合
- 把iter方法時限為生成器,即可定義自己的容器類型
- 想判斷某個值是迭代器還是容器,可以拿該值為參數,兩側調用iter函數,若結果相同,則是迭代器,調用內置的next函數,即可令該迭代器前進一步
用數量可變的位置參數減少視覺雜訊
令函數接受可選位置參數(由于這種參數習慣上寫為*args,所以又稱為star args,星號參數),能夠使代碼更加清晰,并減少視覺雜訊(visual noise)。
visual noise:一種比喻,意思是使代碼看起來不要太過雜亂,以強調其中的重要內容。
出現的問題
- 變長參數在傳給函數時,總是先轉化為元組。這就意味著,如果用帶有*操作符的生成器為參數,來調用這種參數,python必須把該生成器完整迭代一輪,并把所生成的每個值,都放入元組之中。這可能會消耗大量內存。所以只有當我們確定參數個數較少時,才采用這種寫法
- 如果以后要給函數添加新的位置參數,那就必須修改原來調用該函數的那些舊代碼
要點
- def語句中用*args,即可令函數接受數量可變的位置參數
- 調用函數時,可以采用*操作符,把序列中的元素當成位置參數,傳給該函數
- 對生成器使用*操作符,可能導致內存耗盡
- 在已經接受*args參數的函數上繼續添加位置參數,可能會產生難以排查的bug
用關鍵字參數來表達可選行為
def func(arg1, arg2):
return arg1 + arg2
# 以下寫法等效
func(1, 1)
func(arg1 = 1, 1)
func(1, arg2 = 1)
func(arg1 = 1, arg2 = 1)
關鍵字參數的好處
- 易于理解,參數含義與參數值都呈現在面前
- 可以在函數中提供默認值
- 它可以提供一種擴充函數參數的有效方式,使得擴充之后的函數依然能與原有的那些調用代碼兼容
要點
- 函數參數可以按照位置或關鍵字來指定
- 只是用位置參數來調用函數,可能會導致這些參數數值含義不夠明確,而關鍵字參數則能夠闡明每個參數的意圖
- 給函數添加新行為時,可以使用帶默認值的關鍵字參數,以便與原有的函數點用代碼保持兼容
- 可選的關鍵字參數,總是應該以關鍵字形式來指定,而不是以位置參數的形式來指定