
本文含 3758 字, 9 圖表截屏 建議閱讀 10 分鐘
本文是 Python 系列的特别篇的第十四篇
- 特别篇 1 - PyEcharts TreeMap
- 特别篇 2 - 面向對象程式設計
- 特别篇 3 - 兩大利「器」
- 特别篇 4 - 裝飾器
- 特别篇 5 - Sklearn 0.22
- 特别篇 6 - Jupyter Notebook
- 特别篇 7 - 格式化字元串
- 特别篇 8 - 正規表達式
- 特别篇 9 - 正規表達式實戰
- 特别篇 10 - 錯誤類型
- 特别篇 11 - 異常處理
- 特别篇 12 - Collection
- 特别篇 13 - Matplotlib Animation
- 特别篇 14 - All 和 Any
今天星期五,不燒腦了,寫一個簡單的例子以飨大家。例子雖然簡單,但還是有些東西可以學習的。
故事背景:判斷一個正整數是不是質數。
邏輯很簡單,對于一個數 n,隻有從 2 到 n 做個循環,來檢查 n 是不是被每個數能整除,如果是,那麼 n 不是質數;如果不是,n 是質數。簡單明了,代碼如下。
def is_prime(n): for i in range(2, n): if n % i == 0: return False return True
檢驗結果沒問題。
print( is_prime(6) )print( is_prime(7) )
False
如果你就此滿足,就太不 Pythonic 了。你看這上面 for + if + 兩個 return 不覺得醜嗎?反正我覺得醜。是以我準備用 Python 裡面的 all() 函數來實作,先看看 all() 函數怎麼用,用 help(all) 來檢視。
help(all)
Help
從上可知,all() 函數的參數是一個可疊代對象(iterable),像清單、元組、字典等等都是可疊代對象。不清楚的同學請參考【兩大利器】此貼。
當所有元素都為 True 時,傳回 True;隻要有一個元素為 False,傳回 False。
來我們驗證幾組,結果沒問題。
all([1, 2, 3]all([1, 2, 0])all([1, 2, 'all'])all([[], 2, 'all'])
True
這時候需要了解一個知識點,看完就弄懂上面的結果了。
知識點 非布爾型變量也有相對性的布爾值,設該變量為 X
- 當 X 是元素型變量(整型、浮點型)
- 當 X 為 0, 0.0,
bool(X) =
False
- 當 X 非零,
bool(X) =
True
- 當 X 為 0, 0.0,
- 當 X 是容器型變量(字元串、清單、元組、字典、集合)
- 當 X 為空,
bool(X) =
False
- 當 X 不為空,
bool(X) =
True
- 當 X 為空,
代碼驗證:
print( type(0), bool(0), bool(3) )print( type(10.31), bool(0.00), bool(10.31) )print( type(''), bool( '' ), bool( 'python' ) )print( type(()), bool( () ), bool( (10,) ) )print( type([]), bool( [] ), bool( [1,2] ) )print( type({}), bool( {} ), bool( {'a':1, 'b':2} ) )print( type(set()), bool( set() ), bool( {1,2} ) )
class 'int'> False True
好,我們現在開始用 all() 函數來實作找質數,那麼最自然的就是先建立一個空清單,然後檢查 n 是否能被每個元素 i 整除,是的話 n 不是質數,是以清單附加 False 值,反之清單附加 True 值。
def is_prime(n): divisible = [] for i in range(2, n): if n % i == 0: divisible.append(False) else: divisible.append(True) return all(divisible)
結果雖然沒問題,但更醜了,這怎麼越改還越差了呢?
print( is_prime(11) )print( is_prime(12) )
True
來做點改進,直接在清單上附件表達式 n%i != 0,和上面的代碼等價。
def is_prime(n): divisible = [] for i in range(2, n): divisible.append(n % i != 0) return all(divisible)
結果沒問題,但代碼還是醜陋冗長。
print( is_prime(13) )print( is_prime(14) )
True
Hello,這可是生成清單啊,那用清單解析式(list comprehension)啊!不清楚的同學請參考【盤一盤 Python 下篇】此貼第 5 章。
def is_prime(n): divisible = \ [n % i != 0 for i in range(2, n)] return all(divisible)
檢驗結果沒問題,而且代碼漂亮了。但是如果 n 很大又剛好不是質數,那麼按照判斷邏輯,我們隻要找到某個 i 使其可以整除 n 就已經可以判斷 n 不是質數了,但是寫成清單解析式我們還要把整個清單運作完!
print( is_prime(15) )print( is_prime(16) )
False
一個一個判斷,隻要到達某點就停,你想到了什麼?對,生成器(generator)!
生成器是按需求調用 (call-by-need) 的,你需要調用一個值,我就 yield 一個值,然後用 next() 更新内部狀态,等待你下次調用。這套流程也稱作惰性求值 (lazy evaluation),目的是最小化計算機要做的工作。在大規模資料時,一次性處理往往抵消而且不友善,而惰性求值解決了這個問題,它把計算的具體步驟延遲到了要實際用該資料的時候。
改下代碼,隻用把清單解析式的方括号 [] 改成生成器的小括号 (),就完事了。
def is_prime(n): divisible = \ (n % i != 0 for i in range(2, n)) return all(divisible)
檢驗結果當然沒問題。
print( is_prime(17) )print( is_prime(18) )
True
那我們來看看兩者的運作效率吧,為了友善比較,定義了兩個函數:
- is_prime_list_comprehension
- is_prime_generator
def is_prime_list_comprehension(n): return all([n % i != 0 for i in range(2, n)])
def is_prime_generator(n): return all((n % i != 0 for i in range(2, n)))
例子 1:我找了個比較大的質數 131071,用 %timeit 魔法指令來顯示運作時間。
哈哈哈哈,傻眼了吧,生成器運作的時間 (13.6ms) 比清單解析式運作的時間 (13.1ms) 長。
再想想,當一個數的質數,兩個函數都要從頭運作到尾哦,13.6ms 和 13.1ms 可以看做一樣的。
例子 2:在找一個比較大的非質數,試試 8191*8191 = 67092481,它肯定不是質數,而且也沒那麼快找出來,因為 8191 是質數,你品一下。
8191*8191
67092481
再看兩者運作對比結果,引起極度舒适。
總結
你光講了 all 函數,那 any 函數呢?類比一下嘛!
all( [x1, x2, ..., xn] ) 是所有 xi 都為 True 傳回 True(all 的含義),反之傳回 False。
any( [x1, x2, ..., xn] ) 是隻要有一個 xi 為 True 就傳回 True(any 的含義),反之傳回 False。
是以,隻要你見到下圖左邊的代碼樣子,你就可以用 all() + 生成器。
隻要你見到下圖左邊的代碼樣子,你就可以用 any() + 生成器。
其實找質數也可以用 any() 函數來實作,代碼如下:
def is_prime_use_any(n): return not any((n % i == 0 for i in range(2, n)))
注意 any() 函數前面加了一個 not,而且表達式從 n%i != 0 改成 n%i = 0。意思就是說如果 n 可以被任何(any 的含義)i 整除,傳回為 True,前面加個 not 就傳回為 False,那麼就不是質數。
我的天啊,繞不繞口?這代碼反不反人性?再回顧一下優雅 all() 函數實作的代碼吧。
def is_prime_use_all(n): return all((n % i != 0 for i in range(2, n)))
說如果 n 不能被所有(all 的含義)i 整除,傳回為 True,那麼就是質數。
Yes, coding style is a progress!
Stay Tuned!
我的新書《快樂機器學習》
在新加坡終于有買了
掃碼進 Lazada 購買
新加坡全島包郵
我的新課《Python 基礎》
掃碼購買
零套路無前戲直接幹貨
上過的都說好