「Python有什麼好學的」這句話可不是反問句,而是問句哦。shell
主要是煎魚以爲太多的人以爲Python的語法較爲簡單,寫出來的代碼只要符合邏輯,不須要太多的學習便可,便可從一門其餘語言跳來用Python寫(固然這樣是好事,誰都但願入門簡單)。緩存
因而我便記錄一下,若是要學Python的話,到底有什麼好學的。記錄一下Python有什麼值得學的,對比其餘語言有什麼特別的地方,有什麼樣的代碼寫出來更Pythonic。一路回味,一路學習。app
談到生成器/迭代器,人們老是喜歡用斐波那契數列來舉例。函數
斐波那契數列,數學表示爲a(1)=0, a(2)=1, a(i)=a(i-1)+a(i-2) (i>=3): 0 1 1 2 3 5 8 13 21 ... 用一句話說,就是第三位數起,當前這位數是前兩位的和。
固然,使用斐波那契來舉例是一個很合適的選擇。學習
那麼,爲何說到生成器/迭代器就喜歡斐波那契,而不是其餘呢?大數據
斐波那契數列有一個特徵:當前這位數的值,能夠經過前兩位數的值推導出來。好比,我知道了第n位數是5,第n+1位數是8,那我就能夠輕易地得出,第n+2位必然是5+8=13。spa
即,斐波那契數是能夠經過推導式得出的。code
就是這樣一種相似於冥冥註定的感受:當前的平行空間,是由你以前的選擇決定的。並且是欽定好的,你接下來的每一步,其實都已經被決定了,由什麼決定呢,由你之前走過的路決定的。對象
那麼,換句話來講,即能由推導式得出的數列,其實均可以用來作生成器/迭代器的例子。例如,煎魚用一條式子y(n)=y(n-1)^2 + 1 (n>=1), y(1)=1
,同樣能拿來當例子。內存
既然這樣,那就用y(n)=y(n-1)^2 + 1 (n>=1), y(1)=1
吧。
一開始,人們的思想很簡單,若是要求y(n)=y(n-1)^2 + 1 (n>=1), y(1)=1
中y數列的第13位,即y(13),那很簡單,就輪詢到13個就行了。
def y(): y_current = 1 n = 1 while n < 13: y_current = y_current ** 2 + 1 n += 1 print(n, y_current)
輸出也挺長的,就截個圖算了:
這個時候,這個代碼是徹底夠用的。接下來,煎魚加點需求(PM般的獰笑):
接下來,函數改爲:
def y(n_max): y_current = 0 n = 0 ret_list = [] while n < n_max: y_current = y_current ** 2 + 1 n += 1 ret_list.append(y_current) return ret_list if __name__ == '__main__': for i in y(13): print(i)
看起來沒什麼毛病,徹底符合要求。可是,問題出如今當函數的參數n_max較大的時候。要多大呢,煎魚嘗試輸出n_max=13000000,即:
if __name__ == '__main__': for i in y(13000000): print(i)
咱們看得出來,這個函數的計算量十分大,以煎魚當前的電腦配置(Macbook pro 2017 i5 8G RAM),等了一兩分鐘還沒結束,只好強行中斷了。
程序爲何卡那麼久呢。由於在函數的邏輯中,程序試圖將13000000個值都計算出來再返回list以供外接輪詢,並且這13000000個一個比一個難算(愈來愈大,指數增加)。同時,該list也佔了龐大的內存空間。
到了這個時候,煎魚終於要引入生成器了。
其實煎魚就加入了一個yield,並稍做修改:
def y_with_yield(n_max): y_current = 0 n = 0 while n < n_max: y_current = y_current ** 2 + 1 n += 1 yield y_current if __name__ == '__main__': for i in y_with_yield(13000000): print(i)
雖然屏幕滾動得很慢,可是起碼是在實時地滾動的。
加入了yield變成這樣,其實就是搞成了一個簡單的生成器。在這裏,生成器的做用有:
for i in y_with_yield(13000000)
的循環中,每一次循環程序纔會進入函數去計算,而不會把所有結果都計算出來再返回暫時給出初步結論:
咱們再來看下生成器的其餘用途吧。
在讀文件或處理文件時使用緩存是頗有必要的,由於咱們老是不知道文件會有多大,文件的大小會不會把程序給拖垮。
煎魚新建一個文件(假設叫test.txt),並往其中寫入文本,運行如下代碼:
def read_file(file_path): BLOCK_SIZE = 100 with open(file_path, 'rb') as f: while True: block = f.read(BLOCK_SIZE) if block: yield block else: return if __name__ == '__main__': for i in read_file('./test.txt'): print(i) print('--------------block-split--------------')
咱們把100個長度分爲一個block,這個block就至關於咱們的緩存:先從文件中讀100個,而後讓程序處理這100個字符(此到處理爲print),再讀下一個100。其中block-split的輸出是爲了讓咱們更好地辯識出block的頭尾。
經過yield瞎搞出來的簡易生成器有一個很大的限制,就是必需要在循環內。
雖然「迭代」和「循環」有關聯,可是當生成器的生成邏輯無比複雜時,好比「推導」的方法已經沒法用數學推導式表達時,或者某種場景下的業務邏輯比較複雜以致於沒法直接經過循環表達時,生成器類來了。
生成器類看起來很簡單,其實就是將煎魚在上面寫的簡單生成器寫成一個類。
重點就是,咱們得找到「推導」,推導在這裏是指next函數 —— 咱們實現的生成器類最重要的就是next()。
咱們來實現上面的y函數的生成器類:
class Y(object): def __init__(self, n_max): self.n_max = n_max self.n = 0 self.y = 0 def __iter__(self): return self def next(self): if self.n < self.n_max: self.y = self.y ** 2 + 1 self.n += 1 return self.y raise StopIteration() if __name__ == '__main__': y = Y(13) for i in y: print(i)
有幾點是須要注意的:
__iter__()
函數,而返回的不必定是self,可是須要生成器接下來,煎魚帶來一段很無聊的表演,來表示__iter__()
函數而返回的不必定是self:
class SuperY(object): def __init__(self, n_max): self.n_max = n_max def __iter__(self): return Y(self.n_max) if __name__ == '__main__': sy = SuperY(13) for i in sy: print(i)
這段代碼的輸出和上一段如出一轍。
這裏照妖鏡的意思,指一個能鑑別某對象(甚至不是對象)是否一個生成器的東西。
提及來,可能會有點多餘並且零碎。
其中有三個函數:
咱們把前面寫過的y(帶yield的函數),和Y(生成器類)導入後,進行實驗觀察:
from use_yield import y_with_yield as y from iter_obj import Y from inspect import isgeneratorfunction, isgenerator from types import GeneratorType from collections import Iterator if __name__ == '__main__': print(isgeneratorfunction(y)) # True print(isgeneratorfunction(Y)) # False print(isgeneratorfunction(y(5))) # False print(isgeneratorfunction(Y(5))) # False print(isgenerator(y)) # False print(isgenerator(Y)) # False print(isgenerator(y(5))) # True print(isgenerator(Y(5))) # False print("") print(isinstance(y, GeneratorType)) # False print(isinstance(y(5), GeneratorType)) # True print(isinstance(Y, GeneratorType)) # False print(isinstance(Y(5), GeneratorType)) # False print("") print(isinstance(y, Iterator)) # False print(isinstance(y(5), Iterator)) # True print(isinstance(Y, Iterator)) # False print(isinstance(Y(5), Iterator)) # True
實驗的結論爲:
Python裏面,range和xrange有什麼不一樣,用哪一個更好,爲何?
對的,就是和生成器有關係,嘿嘿。
先這樣吧
如有錯誤之處請指出,更多地請關注造殼。