全系列在此 https://www.smalldragon.tw/effective-python-series/

目錄

  • 使用概括式(list comprehension)而非 map 和 filter
  • 使用產生器(Generator)運算式取代大型概括式
  • 優先用 enumerate 而非 range
  • 使用 zip 來迭代兩個 list
  • 善用 try/except/else/finally

使用概括式(list comprehension)而非 map 和 filter

許多語言都有 mapfilter,Python 也不例外,例如

a = [1, 2, 3, 4, 5, 6]
squares = map(lambda x: x ** 2, a)
print(list(squares))
# [1, 4, 9, 16, 25, 36]

這段扣就是對 list 所有元素做平方,問題是 map 的可讀性不高,而且 lambda 在 Python 中效率略差

概括式則是長這樣

squares = [x ** 2 for x in a]
print(squares)
# [1, 4, 9, 16, 25, 36]

明顯好讀了不少,而且執行速度實際上比 map 還快(因為沒有使用 lambda),這有詳細比較可以看看

再來,map 可以搭配 filter,例如只把偶數挑出來做平方

squares = map(lambda x: x ** 2,
              filter(lambda x: x % 2 == 0, a))
print(list(squares))
# [4, 16, 36]

有沒有感覺這段扣的複雜,這可能要花你十秒鐘去理解

使用概括式之後,明顯變簡短且好讀

squares = [x ** 2  for x in a  if x % 2 == 0]
print(squares)
# [4, 16, 36]

概括式是你更好的選擇,不僅比較好閱讀,執行速度也不遜色於 map

使用產生器(Generator)運算式取代大型概括式

概括式遇到大型資料時,可能會因為創立一個過於龐大的 list,造成程式當掉,例如我要計算文字檔每一行的字數

lengths = [len(x) for x in open('/PATH/INFO.txt')]
print(lengths)
# > [32, 19, 1, 12, 26]

如果檔案 size 非常大,或是他是一個不會停止的 input,你應該改用 generator 來產生迭代器,按需要產出內容

lengths = (len(x) for x in open('/PATH/INFO.txt'))
print(it)
# > <generator object <genexpr> at 0x0000017BF5C939E8>

然後搭配 next 來一次次觸發 iterator

print(next(lengths))
print(next(lengths))
# > 32
# > 19

generator 強大的地方在於能夠互相組合,例如

length_squ = ((x, x / 3) for x in lengths)
print(next(length_squ))
print(next(length_squ))
# > (1, 0.3333)
# > (12, 4.0)

Scrapy 中就運用了不少這樣的概念,如果妳熟悉 Scrapy 的話,應該對 generator、yield 不陌生

優先用 enumerate 而非 range

當我們要從陣列中同時取出 index 和數值時,如果你比較擅長 C/C++、JAVA,你可能會這樣寫

values = [1, 1, 2, 3, 5, 8]

for i in range(len(values)):
    print(i, values[i])

試著用看看 enumerate 吧,Python 習慣用比較優雅且閱讀的方式取出來

for i, v in enumerate(values):
    print(i, v)

使用 zip 來迭代兩個 list

如果你有兩個 list 要迭代,你可能會這樣寫

arr1 = [3, 4, 5, 6]
arr2 = ['a', 'b', 'c', 'd']
l = min(len(arr1), len(arr2))

for i in range(l):
    print(arr1[i], arr2[i])

很冗長而且不太漂亮

嘗試看看 zip 吧,他會把兩個 list 變成一個 tuple list 的 generator,每次都可以從中取出一個 tuple

for item in zip(arr1, arr2):
    print(item)
# (3, 'a')
# (4, 'b')
# (5, 'c')
# (6, 'd')

再搭配 unpack 技巧 a, b = (3, 'a'),他會把 tuple 解開,分別賦予(assign to)到前面的變數中,效果和 a = 3, b = 'a' 一樣

程式碼就簡短了許多

for a1, a2 in zip(arr1, arr2):
    print(a1, a2)

注意:zip 會在最短的 list 結束,想要執行到最長的 list,可以用 itertoolszip_longest

善用 try/except/else/finally

try/except 跟 JavaScript 的 then/catch 很像,你應該搭配 else/finally 好好地規劃你的程式碼,特別是比較不穩定的功能或是不容易 debug 的部分 :D

import requests

url = 'REQUEST_URL'
try:
    result = requests.get(url)
except:
    print('request fail')
else:
    print(result.text)
finally:
    print('request end')

我蠻少用 else 的,比較常使用 try/except/finally 這三個組合

系列目錄