Skip to content

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 會對小型的整數使用相同的技術,來避免沒必要的重複

Pythonic Object

  • 四種字串表示的特殊方法: __repr____str____format____bytes—— 分別對應內建函式 repr()str()bytes()format()
  • repr 是給開發者看的;str 是給使用者看的

類別實例與 bytes 相互轉換的實作範例


  • classmethod 的第一個引數一定是類別自己(通常命名為 cls),而 staticmethod 則是跟一般函式沒兩樣(不是很實用)
  • format(obj, format_spec) 接受格式指定符(formatting specifier) 作為引數,其使用的語法稱為 Format Specification Mini-Language
  • Format Specification Mini-Language 是可以擴充的,每個類別都可以按照自己喜歡的方式解釋 format_spect 引數,但沒有實作 __format__ 的話,則回傳 str() 且不接受指定符參數
  • 拿來做 __hash__ 的物件屬性最好具有唯獨保護(實例的雜湊值永遠不該改變);在實作上,建議使用 XOR 運算子來混合物件屬性本身的雜湊,例如 hash(self.x) ^ hash(self.y)
  • 在實例屬性命名時,加上前綴兩個底線,在存取會變成例如 _Dog__mood,這種功能叫做名稱重整 (name mangling),用意是防止有人意外存取,或是不當覆寫——這作法並不受歡迎,還不如單純用一個底線當前綴就好(反正你知我知大家知不要去動它)
  • __slot__ 會讓解譯器將實例以 tuple 保存而不是 dict,藉此節省記憶體開銷,但此設定無法被繼承,適合用在表格資料、結構描述(schema)被定死且資料集眾多的資料庫紀錄上
  • 實例只能擁有 __slot__ 指定的屬性,但不要笨笨的在裡面加上 __dict__ 那樣就沒屁用了
  • 「到處使用 getter/setter 呼叫式很蠢」—— 在 Python,單純使用公開屬性就好,如果須要,可以把它們改成 property 來做寫入保護
  • Java 的 privateprotected 修飾器通常只能防止不小心的行為(即安全性),它們只能在應用程式使用 security manager 來部署時(通常很少),才可以保證能夠防止惡意入侵

序列修改、雜湊、切片

  • 實作容納大量項目的集合型態,一個重點是簡寫 repr() 的輸出,可以直接現有的工具 reprlib.repr 來做
  • 在物件導向的環境中,協定 (protocal) 是一種非正式的界面,只會被定義在文件中,而不會被定義在程式中。例如:Python 的序列協定只涉及 __len____getitem__,我們不用明確宣告它是序列,只要它有序列的行為就夠了,這就是 duck typing

一個好的 __getitem__ 實作範例


  • 要製作 Pythonic 的物件,就要模擬 Python 自己的物件——「模擬的程度只要合理地符合被塑造的物件就可以了」、「只實作部份協定可以讓事情保持簡單」
  • 當你實作 __getattr__ 時,通常也要一起實作 __setattr__ 來避免物件之間有不一致的行為
  • 使用 reduce 時,提供第三個引數 (initializer) 作為初始值可以避免因傳入空序列而導致的例外
  • mapzip 回傳的的都是 generator
  • zip 的命名來自拉鍊 (zipper fastener)
  • 「如果你想要總和一串項目,就應該在編寫時,讓它看起來像『一串項目的總和』,而不是像『對這些項目執行迴圈,使用令一個變數,執行一系列的加法』」——僅作為參考用,每個人心中對 “idiomatic Python” 有不同的認知

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *