Python 冷知識 - 註解、函數、布林值

匯整一下近期整理的 Python 冷知識,就是那些知道了也不一定會變高手的趣味事實(至少我覺得有趣)。

關於多行註解

有寫過 Python 的朋友應該知道,在 Python 是使用 # 符號來當做程式碼「註解(Comment)」寫,但你可能不知道 Python 並沒有「多行註解」的設計喔!
你可能會好奇這樣的寫法:

print("Hello")

"""
這是註解
這也是註解
這行還是註解
"""

print("World")

你看,用 3 個引號這個不就是多行註解了嗎?

如果你認為這是註解,那你可能誤解「註解」的設計了。

在 Python 這種 3 個引號的寫法並不是註解,它只是可以換行的「多行字串」而已。Python 本身只有單行註解,並沒有多行註解的設計。

的確,你可以拿多行字串來充當註解的效果,但不表示它就是註解,說到底它就只是個字串而已。通常使用多行文字充當註解的時候都不會指定變數給它,所以在 Python 直譯器的眼裡,它可能不會被編譯到 Bytecode 裡,以剛才的範例,編譯出來的結果是:

  0           RESUME                   0

  1           LOAD_NAME                0 (print)
              PUSH_NULL
              LOAD_CONST               0 ('Hello')
              CALL                     1
              POP_TOP

  3           NOP

  9           LOAD_NAME                0 (print)
              PUSH_NULL
              LOAD_CONST               1 ('World')
              CALL                     1
              POP_TOP
              RETURN_CONST             2 (None)

可以看到中間的多行文字直接消失了。

是說,如果把這個寫法放在類別或函數裡的第一行,可以造成 Docstring 的效果,也就是會幫類別或函數的 __doc__ 屬性加料,但如果只是要做到 Docstring 的效果,一般的字串就能做到了,不一定需要用到多行字串。

Python 的發明人 Guido 曾經對多行註解這樣回覆過:

如 Guido 所說,的確是可以拿多行文字來「當做」多行註解,但這同時也表示 Python 本身就是沒有多行註解的設計,如果有的話就不需要用多行文字來「當做」了。

type() 不是函數

如果想要在 Python 裡印出某個資料的型別,可能會使用 type() 這個函數(或函式),如果想要做數字或字串轉型可能會用 int()str() 函數,像這樣:

>>> type(123)
<class 'int'>

>>> int("123")
123

>>> str(123)
'123'

但是,事實上不管是 intstr 或是 type,它們在 Python 裡都是一個「類別(Class)」,只是剛好只傳一個引數給它的時候,它會做一些特別的事,例如做型別轉換或是印出它的型別,不信的話你可以用 type() 檢驗看看:

>>> type(int)
<class 'type'>

intstrlistdict 等類別都是一種叫做 type 的型別,這是因為 type 型別在 Python 是所有內建類別的 meta class,也就是所有內建型別都是一種 type,包括 type 自己也是:

>>> type(type)
<class 'type'>

雖然它們用起來的手感像是呼叫函數,但在 Python 裡它們都是實實在在的類別 :)

關於布林值

在 Python 裡的布林型態(bool)的 TrueFalse,有些網路上的教學會說它們可以被轉型成數字 10。這樣的說法沒問題,但可能比較少人知道在 Python 裡布林值其實就是一種數字。不信的話,可以試著這樣做:

>>> bool.__base__
<class 'int'>

__base__ 屬性可以取得類別的上層類別,所以可以看的出來 bool 的上層類別就是數字 int,它們之間是有繼承關係的,所以布林值就是一種(kind of)整數。

而且,在 Python 裡的 TrueFalse 都只會存在一份,而且還是直接內建在 Python 直譯器裡。不管你怎麼建立新的布林值物件,它都會指向同一顆物件。例如:

>>> a = bool(123)
>>> b = bool("xyz")
>>> a is b
True
>>> a is True
True

可以看到它們就是同一顆物件。

另外,如果程式碼中使用了一看就知道的布林值判斷,例如:

if True:
  print("這是真的")

因為這一定會發生,所以 Python 編譯的過程會直接把 if True 的判斷拿掉,只留下底下的程式碼。反之,如果是這樣寫:

if False:
  print("這根本不會發生")

因為這一定不會發生,所以這段程式碼直接會被 Python 無視,根本不會被編譯成 Bytecode。

最後再補個冷知識,在 Python 2.2 之前,Python 其實是沒有布林型態的,早期的開發者常會使用 0 和 1 來代替布林值判斷,或是自己定義 TrueFalse,例如 True = 1 或是 False = 0 這樣的寫法。直到 PEP 285 提案之後才正式引入布林值的設計,並將上層類別設定成 int 類別。不過,在 Pytohn 2 時代,就算 PEP 285 提案已經通過,TrueFalse 還是可以被重新定義:

# 注意,這是 Python 2
>>> True = False
>>> print(True)
False

直到 Python 3 之後,TrueFalse 才變成保留字,無法被重新定義。