day_01: 我不會寫 Python


這句簡單的話,我直到三十多歲,才說了出來。

我一直以為,學寫程式很簡單,找個問題,然後分解,然後找尋每個子問題當中,對應的語言特性與 API 來呼叫就好。

直到去年,某個靈光乍現的一刻,我突然意識到,其實我根本不會 Python。即使用它來寫過一些工具,即使用 Jupyter 玩過一些 machine learning 練習,況且我還有良葛格的 Python 技術手冊。但是這個語言的本質,其實我並不了解。

於是,人一旦承認自己的無知,便能開始從 0 到 1 的路。

那要如何掌握一個語言呢?

Language Spec

有些語言的規格書很厚,不容易印出來,像 Java 或 Haskell;有些語言的規格書比較薄,我通常會印出來讀,像 Scheme。但 Python,有語言規格書嗎?

有的,Python Language Reference (以下簡稱 「PyLR」)。請把它轉成電子書放在 Kindle 的第一本,或把它的連結放在 Facebook 的置頂,最後請把它印出來,放在馬桶旁、枕頭旁、還有通勤坐車上班吃飯,垂手可得之處。

為何要讀語言規格書,找一本天瓏排行榜上的書或去 StackoverFlow 不就好了嗎?

這很簡單,當你想了解貝多芬時,你會反而跑去聽布拉姆斯,舒伯特,或者是華格納嗎?不,你會聽貝多芬。又或者你會去看坊間一些音樂科普書,介紹貝多芬生平,介紹他指揮完第九號時的歡聲如雷嗎?你或許會,我也會,但最好的辦法仍然是:去聽貝多芬,聽不同時期、不同指揮下的貝多芬,若一次沒辦法聽太多首,就聽一首。如果有受過樂理的訓練,那麼能讀讀譜會更好。

當我想認識一個語言時,或許會讀 Learn You A XXX for Great Good,也會找找 StackoverFlow 或 GitHub 上的 Awesome XXX。但若要能真的掌握它,一定得回到規格書的正統脈絡下,使用規格書作者所使用的語彙來認識這個語言。

對我而言,有了語言規格書後,從哪裡開始呢?

Python 的 typing

拿到一個語言,我通常會先了解它資料的表示方式。這方式正式點的名稱,就是型別系統。透過型別,我們可以區分語言中的每一件事物是什麼與不是什麼。

而 Python 有哪些型別呢?其實 Python 的型別系統很豐富,從 PyLR 的分類看得出來,至少分為 13 大類:

  1. None
  2. NotImplemented
  3. Elipsis
  4. numbers
  5. Sequences
  6. Sets
  7. Mappings
  8. Callable types
  9. Modules
  10. Custom classes
  11. Class instances
  12. I/O Objects
  13. Internal types

至於為何會分成這麼多,我的理解是,Python 是一種腳本語言,因此它的設計本來就是出於彈性、靈活、方便,甚至是直觀。因此,許多在物件導向語言裡,透過類別、物件與註解 (annotation) 來達成的概念,在 Python 裡面直接化成語言的一部份,進入了型別系統。

Python 的 operator overwrite

在 Python 裡面,如同 PyLR 第三章開宗明義所說,每一個事物都被當成物件 (object) 來看待。因此,每個事物像是物件一般,有屬於自己的方法,而這其中有著一些共同、且特殊的方法。在這其中有一群很特別,它們提供物件可以在語法上轉為邏輯判斷或數值運算的作用:

方法 運算子
__lt__() <
__le__() <=
__eq__() ==
__ne__() !=
__gt__() >
__ge__() >=

以及

方法 運算子
__add__() +
__sub__() -
__mul__() *
__matmul__() @
__truediv__() /
__floordiv__() //
__mod__() %
__divmod__() divmod()
__pow__() pow()**
__lshift__() <<
__rshift__() >>
__and__() &
__xor__() ^
__or__() |

了解這些 operator overwrite 的特殊函式,並理解 Python 型別的概念後,大概就不難理解在 Pandas 裡,如何用這種寫法,過濾出年紀大於 20 歲的人:

adult = people_df[people_df.age > 20]

# 或

adult = people_df[people_df['age'] > 20]

結語

雖然 Python 有這麼多型別跟特殊用法,但我仍想問一個問題,這些型別與特殊函式提供了豐富且彈性的語言特性,但能不能更加嚴謹些呢?

例如在 Python 裡,這樣寫是可以的:

def test():
  a = 1
  print('a: ', a)  # a: 1

  a = 'a'
  print('a: ', a)  # a: a

在具有型別推導的語言裡,當你把一個值 (value) 與一個名稱 (identity) 綁定 (binding) 在一起後,這個值的型別 (type) 就會成為這個名稱的型別。而在許多具有這類特性的語言裡頭,當一個名稱被綁定了值與型別後,它便無法再改變,例如 Kotlin:

var a = 1
println("a: " + a)  // a: 1

a = "a"
println("a: " + a)  
// error: type mismatch: inferred type is String but Int was expected

很遺憾的是,我無法找到可以限制這種寫法的方式,甚至連 Rust 對型別系統這麼嚴謹的語言 (繼承 OCaml 而來的靜態強型別),都允許開發者重覆定義同樣的 identity:

let a = 1;

// ...

let a = 'a';  // 這是可以的

// ...

我只希望在我寫 Python 的歲月裡,可以避開這些奇葩的語言特性,寫出嚴謹易讀的程式。

#Python







你可能感興趣的文章

D24_ ALG101-Unit 5

D24_ ALG101-Unit 5

同步 & 非同步(2) - event loop & macro / micro task

同步 & 非同步(2) - event loop & macro / micro task

Python 程式設計函式的 內建函式和自訂函式 入門教學

Python 程式設計函式的 內建函式和自訂函式 入門教學






留言討論