Skip to content

Fluent Python 讀書筆記(五)

  • Python

控制流程

  • 在大部分情況下,Python 社群將 Iterator 與 Generator 視為同義詞
  • Python 所有集合都是可迭代的
  • 內部的 for 迴圈、集合生成式、變數和引數的 Unpacking 都會用到 Iterator
  • iter() 會先參考 __iter__,其次才參考 __getitem__,都沒有的話,發出 TypeError 代表「該物件不可迭代」(此處 __getitem__ 的參考在以後可能被棄用)
  • 承上,可迭代物件不一定滿足 isinstance(C, abc.Iterable)(在未實作 __iter__ 的情況下),為了避免這個誤區,要判斷物件是否可迭代,最準確的方式是呼叫 iter() 看看
  • 如果 iter() 會過,那物件是「Iterable」;實作 __iter__ ,須回傳一個「Iterator 實體」—— Python 會跟 Iterable 索取 Iterator
  • Iterator 類別的標準介面:__iter____next____next__ 負責回傳下一個項目或發起 StopIteration,__iter__ 則單純回傳 self
  • 不要把 Iterable 跟 Iterator 混為一談,「Iterable 有一個 __iter__ 方法,這個方法每次都會實例化一個新的 Iterator」
  • Iterator 也是 Iterable,但 Iterable 不是 Iterator。Iterable 永遠不該扮演自己的 Iterator
  • Iterator 獨立出來的用意是「每一個迭代器都能保存它自己的內部狀態」
  • 除了回傳獨立的 Iterator 實體,也可以將 __iter__ 變成一個「Generator 函式」,藉由回傳一個「Generator 實體」,以介面而言,Generator 是 Iterator,它會在內文結束時發出 StopIteration
  • 用一個「 lazy 的產生器」取代一個「儲存所有資料的迭代器實體」是更好的,因為只要在必要時(最後一刻)才產生值,可以節省大量記憶體
  • Iterator 的另一個功能是「延緩工作」、「一次只產生一個項目」
  • 「當你在用 Python3 想著『有更 lazy 的作法嗎?』的時候,答案通常都是『有』」
  • yield from 不只是一個糖衣語法,除了取代迴圈之外,它也是一個管道,連接外部產生器,接收外部產生器的值
  • allany 有一種重要的優化是 reduce 無法作到的,那就是 short-circuit,確定結果後就停止
  • sorted 可以接收任意的 Iterable
  • iter() 的另一個功能:傳入一個 Callable 及一個標記值(sentinel),當回傳值等同此標記時,停止迭代
  • 無論資料大小為何,Generator 提供一種有彈性的解決方案,把大型資料集當做資料流來處理
  • .send() 同樣會讓產生器進入下一個 yield,但是它也可以用來傳入資料,相較於 next() 單純接收資料,.send() 可讓使用者與產生器雙向交換資料——變成協同程序 (coroutines)
  • 「在內文埋入一個 yield,不足以提醒那一個語意有如此不同」(但 Guido 討厭使用新的關鍵字)
  • 以實作而言,Generator 是一種語言結構,以函式或表達式編寫,呼叫時回傳 GeneratorType
  • 以概念而言,不管 Iterator 內部有多複雜(例如是一個樹狀資料結構),它的資料永遠只有一個來源(自己本身);至於產生器,則不一定只產生集合裡面的項目
  • 「Iterator 最簡單的介面是由 First、Next、IsDone、CurrentItem 的操作組成」,在 Python 它的介面更精簡:next()StopIteration

Read More »Fluent Python 讀書筆記(五)

Fluent Python 讀書筆記(四)

  • Python

介面:從協定到 ABC

  • 「抽象類別代表介面」
  • Python 自 2.6 版本之後加入 ABC (abstact base class),大多被定義在 collections.abc 模組
  • 當你需要實作介面時,第一步是將它們當成超類別 (superclasses),ABC 會檢查具體子類別是否符合這個介面
  • ABC 與描述器 (descriptors)、中繼類別(metaclasses)一樣,是建構框架的工具,過度使用 ABC 的風險是非常高的
  • 可以把介面想成「某個物件的公用方法的子集合(subsets)」,這個子集合可以在系統中發揮具體的作用(常在文件看到 “a file-like object”、”an iterable” 的字眼都是在指涉這件事)
  • 協定(protocal) 是非正式的介面,只由文件與慣例定義,無法被強制實施,例如:選擇只實作序列協定的某些方法如 __getitem__,而不是繼承 abc.Sequence
  • Python 資料模型的哲學,就是盡可能地與基本協定合作
  • isinstance(obj, cls) 沒有那麼糟,只要 cls 是一個 ABC
  • 所謂的 goose typing ,是相對於協定的 duck typing,鼓勵我們可以去實作 ABC 的介面(透過繼承而非自造輪子)
  • Python ABC 有類別方法 register 可以讓使用者「宣告」某個類別是 ABC 的一個「虛擬子類別 (virtual subclasses)」,而不用實際的繼承,簡單來說就是讓 Python 相信我們會實作介面而不實際檢查(如果有任何問題,就讓在執行階段拋出例外吧)
  • 除了透過函式呼叫來註冊,在 Python 3.4 之後提供了類別修飾器 @<ABC classname>.register
  • 有些子類別不一定要明確的註冊或繼承,也可以成為特定 ABC 的子類別,例如 __len__ 之於 abc.Sized(背後是透過 __subclasshook__ 來實現的,類似的實作少之又少)
  • 不要在程式中自訂 ABC 或 metaclass」—— 從 ABC 繼承方法比實作需要的方法還要好,ABC 的目的是封裝因為框架而產生的一般性、抽象概念,例如這是一個「序列」與「確切的數字」
  • 「ABC 的流行可能是個災難,它對語言施加過度的儀式」
  • numbers 裡面定義了數值的 ABC,最頂層的超類別是 numbers.Number

image

  • IndexErrorKeyError 都是 LookupError 的子類別
  • 宣告 ABC 有兩種方式: 1. 繼承 abc.ABC(3.4 之後才加入) 2. 指定 metaclass=abc.ABCMeta (3~3.4 的限定作法)
  • 諸如 @abstractclassmethod 的冗員裝飾器已被 ABC 棄用,要用的話,只要單純疊加 @classmethod@abc.abstractmethod 即可(要注意順序)
  • 「雖然 ABC 有助於型態檢查,但不應該過度使用它。Python 的核心是動態語言,到處限制型態,可能會讓程式變成沒必要的複雜」
  • 型態提示 (type hints) 是註釋的一種,可以在函式定義中指名參數的型態及回傳何種型態,沒有強致力

Read More »Fluent Python 讀書筆記(四)

以其人之道還其人之身:如何用分治法對付 LeetCode / 刷題心得跟題庫分享

  • Misc

Intro

是的,當你看到這篇文章的時候,我還沒找到工作。因此,最近的幾個月,我如果不是在刷題,就是在沒網路的路上。

截至目前為止,我嘗試了將近 300 種不同題形的解法,有效題數是 209,約 1000 多次 commit。

刷題時間久了,對 LeetCode 這個東西不禁感到又愛又恨。甚至是恨的層次居多,為什麼?我認為有以下幾個原因:

真的用得到嗎?

當然,像 146. LRU Cache253. Meeting Rooms II 這種貼近實務的題目是再好不過了,但你不得不承認,大部分的題目還是有那麼派不上用場的意味在,那種感覺就像你在高中學了艱澀的數學理論,但並不知道怎麼在真實世界中應用它們一樣。

試想一下,你拿刷題的時間去嗑一本有關程式設計的書,或去想辦法弄些什麼專案,學到的「馬上可用的知識點」應該都比刷題多得更多。

應徵的公司在場上覆蓋三張陷阱卡,其中一張是神的宣告

你有沒有覺得,現在好多公司都好愛考刷題,先是聯繫到你,告訴你說「我覺得你很可能就是我們要找的 XXX」,然後也不跟你約時間,就丟一個考試連結給你。

然後你可能沒辦法在 20 分鐘內「把零移到前面」或找不到「買賣股票的進出點」,然後就失去了面試機會,或者再也沒有下文。這代表了一件事——開發者的價值被 LeetCode 給過度簡單地歸類了——如果你不刷題,不管你曾經開發過什麼,都不再有意義,因為你連門票都拿不到。

這種面試體驗,我至少就遇到過三次。

是的,即使你知道什麼是陣列、什麼是 swap、indexing,你也知道時間複雜度跟空間複雜度是在講什麼,但你就是想不出來怎麼把該死的 0 給移到前面去。

還記得 Homebrew 的開發者 Max Howell 因為不會翻轉二元樹被 Google 拒絕了,他氣得發了一篇反諷推文。不過他事後表明:我某種程度來說也很爛,我還是搞不太懂二元樹是什麼,但我不怪 Google 了。

這個例子可以很好地概括我目前對面試考題的想法:Get Over It。

當刷題儼然成為找工作的「內建」、必要條件,那麼也許,我們就不要再 ㄍ一ㄥ 了吧。

Read More »以其人之道還其人之身:如何用分治法對付 LeetCode / 刷題心得跟題庫分享

Fluent Python 讀書筆記(三)

物件參考、可變性與重複使用

  • 「變數是標籤,不是盒子」
  • 使用參考變數 (reference variable) 時,說「變數被指派給一個物件」會比較合理,畢竟——物件是在賦值之前建立的
  • 兩個變數被指派到同一個物件時,這兩個變數互為「別名(alias)」
  • 「每一個物件都有一個身份(ID)、一個型態跟一個值」,在 CPython,這個身份是 id(),回傳物件的記憶體位置(不同解譯器可能會使用不同東西作為 ID)
  • == 比較物件的值;is 比較物件的 ID
  • is== 快,因為它無法多載(不需要尋找或呼叫特殊方法來演算出一個值)
  • 原始物件的 __eq__ 會比較 ID,但大多數覆寫 __eq__ 的情況通常會加入或使用別的比較
  • tuple 不可變的意思是「保存在它當中的物件參考 ID 不變」,即使 tuple 可能存了可變的物件
  • 淺複製 (shallow copy) 即容器本身會被複製,但新的容器裡面保存的是舊的參考,例如 arr[:]arr.copy()copy(arr)
  • 實作 deep copy 要小心物件可能會循環參考 (Ring),要判斷物件是否已經複製過
  • 覆寫 __copy____deepcopy__ 可以控制 copy.copy()copy.deepcopy() 的行為
  • Python 函式傳遞的是參考(call by sharing) —— 即函數的參數 (parameter) 會指向引數 (argument) 的參考,換句話說,「函式內的參數就是其實際引數的別名」
  • 同上,這也是為什麼「函式的預設參數不要使用可變型態」,簡單的改良:預設為 None,在函式中判斷是否初始化新的可變物件
  • del 刪除的是參考,而不是物件本身;物件只有在「參考數量變成零」的情況下才有可能被回收,這種銷毀可能不是立即性的
  • CPython 回收記憶體的演算法主要是計算參考數量,這個參考數量存在物件本身,但假若有循環參考時,容易發生 memory leak
  • 在 CPython 的實作下,對 tuplestrbytes而言 s[:] 不會製作複本,而是回傳物件的參考
  • 在使用執行緒時,修改可變物件很難得到正確的結果:無法適當同步的執行序,會導致資料損毀;過度同步的執行序,會造成 deadlock

弱參考 (Weak Reference)

  • 常用在使用快取的情境下,須要「參考一個不會被保存太久的物件」
  • 弱參考是一種可呼叫的物件,它會回傳參考的物件,或者 None
  • 使用弱參考而非賦值,就不會讓物件的「參考數量」增加
  • 考慮使用 WeakKeyDictionaryWeakValueDictionaryWeakSetfinalize 這些內部使用弱參考的高階界面,而非自己用 weakref.ref 實作
  • 因為實作的限制,listdict 的子類別可以被弱參考(原始型態不行),而 inttuple 則完全無法被弱參考


字串常值的共用,是一種優化技術,稱為 interning,Cpython 會對小型的整數使用相同的技術,來避免沒必要的重複

Read More »Fluent Python 讀書筆記(三)

Fluent Python 讀書筆記(二)

一級函式

  • 在 Python 所有函式都是一級物件:
    • 可在執行階段建立
    • 可以指派給變數,或資料結構內的元素
    • 可以當成引數傳給函式
    • 可以當成函式的結果回傳
  • 如果一個函式的引數是包含函式,或回傳的物件是函式,它就是高階函式 (higher-order function),經典的例子是 mapfilterreduce
  • listcomp、genexp 可作 mapfilter 的工作,且更容易閱讀,後兩者已經沒有那麼重要了
  • 除了用來處理高階函式的引數外,匿名函式在 Python 並沒什麼其他用處,且通常難以閱讀
  • Python 的七種 callable
    1. User-defined functions: deflambda
    2. Built-in functions:以 C 寫成的函式,如 lentime.strftime
    3. Built-in methods:以 C 寫成的方法,如 dict.get
    4. Methods
    5. Classes: 透過 __new__ 建立,再經 __init__ 初始化
    6. Class instances:須實作 __call__(任何物件都能有函式的行為)
    7. Generator functions
  • 如同自訂類別的實例,函式會使用 __dict__ 儲存特定的使用者屬性
  • 幾個重要的函式專用特殊方法:`
    • __annotations__:參數與回傳註解
    • __closure__:綁定自由變數(free variables)的空間
    • __code__:中繼資料、及編碼後的函式內文
    • __defaults__:以 tuple 儲存正式參數的預設值
    • __kwdefaults__:以 dict 儲存限關鍵字的正式參數的預設值
  • 要知道函式需要什麼參數、以及有沒有預設值,使用 inspect 模組會比較方便。因為 __(kw)defaults__ 雖然儲存了預設值,但參數名稱卻是放在 __code__ 裡面,必須由後往前掃描一次,才能將每一個值與各自的參數連結

  • inspect.Signature 物件有一個 bind 方法可以拿來測試傳入的參數組合
  • 函式註釋(Function Annotations)常見的型態是類別,如 strint,或字串如 int > 0,註釋不會處理任何工作,會被保存在 __annotation__ 屬性裡
  • 對解譯器來說,註釋沒有意義,它只們是可能會被工具所使用的中繼資料
  • Guido 清楚地表示不想讓 Python 成為 Funtional Programming 語言(但是因為有 operatorfunctools 模組,可以善加運用在 FP 風格上)
  • 列出一些有用的 FP 工具:operator.itemgetteroperator.attrgetteroperator.methodcallerfunctools.partial
  • 在 Python 中廣泛採用 FP 語法的最大障礙,就是缺乏尾部遞迴消除 (tail-recursion elimination) 的最佳化功能
  • 「所有匿名函式都有一個嚴重的缺點:它們沒有名字」

Read More »Fluent Python 讀書筆記(二)

公園生活

  • Quote

只要在公園長椅上發呆的時間一久,就會發現風景這種東西其實要有意識才看得到。泛著漣漪的池子、長著青苔的石垣、樹木、花朵、航跡雲,這一切雖然盡收眼底,其實等於視而不見,只有在意識到其中一樣事物,例如意識到在池面上游泳的水鳥時,水鳥才會從周遭的一切中切割出來。

她的耳垂上,該說是耳環嗎,嵌著一個大小像戒指般的粗厚金屬,開了個圓洞。空洞的另一邊,看得到駒澤路上塞住的車陣。

新聞畫面,特別是播報戰禍的影像,關掉聲音來看的話,人類就只是一具具軀體,這在螢幕上成了一件再自然不過的事,給人一種新的衝擊。一旦轉開音量,不論是賓拉登、布希、包威爾、夏隆、阿拉法特,或是新聞旁白,吐出的都是連串艱澀的詞句,彷彿那些詞句孕育出思考,而這孕育出的思考又在醞釀著什麼。如果把聲音關掉,就完全無法看出人類的思考了,所看到的只是或走、或坐、或躺的人類軀體罷了。

穿了五年的運動褲鬆緊帶已經鬆了,只要一邁出步伐就會往下滑落。我想把褲繩綁緊,不過一邊褲繩的前端縮到繩口裡去,找不出來了。

錄放影機的時鐘顯示我打電話的時間是「20:34」,掛上話筒後是「20:43」。差一分鐘就正好十分鐘了,我並不是說要用那一分鐘來說什麼,只是覺得應該可以用那一分鐘再說些什麼。

她一派輕鬆的模樣,彷彿身上若是掛了條圍裙,八成還會在上面抹抹手吧!

簡而言之,只要在插花的時後想像花器上方有個透明的球體就行了,而花辨識插在那個球體的內部。從花器往上方正中間延伸的花材稱為「真枝」,搭配真枝有朝左延伸的「副枝」,和向右延伸的「體枝」。插花就是由這三種役枝所構成的。

只要和鞠子在一起,就會覺得整個人鬆懈下來。那是一種和別人在一起無法體會到的奇妙感受,我的眼前浮現人造衛星脫離軌道後,失去衝勁的背影。

她希望我放下沈重的貨物,輕盈地四處飛躍。不過我卻害怕真要這麼做的話,就會如同漂浮於大海中的泡沫,不論隨波逐流到哪裡,最終都會破滅消逝。

殘酷的語言多半都是出自於浮著笑意的雙唇間。