###### tags: `Python 入門` 物件的命名 (naming) === 撰寫程式時, 能夠讓程式好閱讀, 也就是容易看得懂是很重要的事, 否則即便是自己寫的程式, 也有可能幾個月後就看不懂, 因此在這一小節中我們要先學習幫程式中的物件命名。 ## 物件的名稱 在前面的例子中, 曾經用 Python 計算挑選特價書的總價格: ````Python >>> (100 + 200) * 0.7 + (200 + 300) * 0.8 610.0 ```` 如果想讓這個運算式更容易看懂, 同時防止寫錯複雜的運算式, 可以用**具有意義的名稱**來代表計算過程中的個別項目。例如, 用 `thirty_off_books` 代表 7 折書的金額、`twenty_off_books` 代表 8 折書的金額, 並用 `total` 代表總金額, 那就可以將上面的 Python 運算式改成這樣: ```python= >>> thirty_off_books = (100 + 200) * 0.7 >>> twenty_off_books = (200 + 300) * 0.8 ``` 第 1 列中由 `=` 建構而成的整列內容稱為『**指派敘述 (assigment statement)**』, 會將 `=` 右邊的運算式 `(100 + 200) * 0.7` 運算結果所得到的物件 `210.0` (還記得嗎?Python 中所有的資料都是物件) 以左邊指派的名字, 也就是 `thirty_off_books` 來命名, 命名之後只要用到 `thirty_off_books` 時就代表取用 `210.0`;同理, 第 2 列就表示用 `twenty_off_books` 這個名字代表 `(200 + 300) * 0.8` 的運算結果, 也就是 `400.0`: ```graphviz digraph object { var1 [label="<name> thirty_off_books" shape=record]; obj1 [label="{<value> 210.0|}" shape=Mrecord]; var1:name -> obj1:value var2 [label="<name> twenty_off_books" shape=record]; obj2 [label="{<value> 400.0|}" shape=Mrecord]; var2:name -> obj2:value } ``` :::info :memo: 幫物件命名很像是幫物件用繩子綁上名牌, 在 Python 裡稱為『**命名 (naming) 與綁定 (binding)**』, 也很像是我們參加活動時主辦單位要我們掛在身上的識別卡。 ::: :::info :memo: Python 的程式是由一個個『**敘述 (statement)**』構成, 由這些敘述來控制程式的流程。個別的敘述有特定的寫法, 後續遇到時就會再說明。 ::: 指派敘述並不像運算式會產生運算結果, 因此在交談模式中不會顯示任何內容。你可以在交談模式下鍵入名稱, 就可以看到該名稱所對應的物件: ```python >>> thirty_off_books 210.0 >>> twenty_off_books 400.0 ``` :::info :memo: 顯示時數值尾巴的 `.0` 表示這是一個浮點數物件, 而不是整數物件 ::: 利用相同的方式, 可以將這 2 個物件加總後得到的新物件, 命名為 `total`: ```python >>> total = thirty_off_books + twenty_off_books >>> total 610.0 ``` 當上述程式執行時, 會根據 `thirty_off_books` 與 `twenty_off_books` 這 2 個名稱找到它所代表的物件, 從物件中取出值後加總, 將加總結果放入新的物件中, 並將這個物件取名為 `total`: ```graphviz digraph object { var1 [label="<name> thirty_off_books" shape=record]; obj1 [label="{<value> 210.0|}" shape=Mrecord]; var1:name -> obj1:value var2 [label="<name> twenty_off_books" shape=record]; obj2 [label="{<value> 400|}" shape=Mrecord]; var2:name -> obj2:value var3 [label="<name> total" shape=record]; obj3 [label="{<value> 610.0|}" shape=Mrecord]; var3:name -> obj3:value } ``` 當物件具有名稱時, 例如剛剛的 `total`, 我們就簡單的說 `total 的值` 來表示該名稱所代表物件的資料值, 也就是 `610.0` 了。 :::warning :warning: 如果你真的想, 也可以用中文來幫物件取名字, 例如前面的程式也可以改成這樣: ```python >>> 七折書金額 = (100 + 200) * 0.7 >>> 八折書金額 = (200 + 300) * 0.8 >>> 七折書金額 210.0 >>> 八折書金額 400.0 >>> 總金額 = 七折書金額 + 八折書金額 >>> 總金額 610.0 ``` 不過一般來說, 因為 Python 程式中會有大量的英文單字以及運算式, 中英夾雜時比較不容易閱讀, 而且如果要和其他國家的人合作撰寫程式, 中文字恐怕大多數人都無法讀懂, 所以我們並不建議大家使用中文來為物件命名。 ::: :::warning :warning: 在為物件命名時, 名稱中不能有空格或標點符號, 但可以使用 `_` 符號, 也可以使用數字, 但是數字不能在名稱的開頭, 所以 `thirty_off_books` 是合乎規則的名字, 但是 `30_off_books` 就不符合。 Pyhton 官方建議名稱都用小寫, 如果需要多個單字結合時, 單字與單字之間就以 `_` 連接。有關 Python 官方建議的程式寫法, 可以參考 [PEP8 文件](https://www.python.org/dev/peps/pep-0008/)。 ::: ## 每個物件可以有多個名稱 物件在命名後並不是就此綁死, 如果有需要, 你還可以幫同一個物件取不同的名字, 就像是同一個人身上可以掛多張名牌一樣。例如: ```python >>> payment = total >>> payment 610.0 ``` 第 1 列的意思就是為 `total` 所代表的物件再綁上一個名牌叫做 `payment`: ```graphviz digraph object { var1 [label="<name> total" shape=record]; var2 [label="<name> payment" shape=record]; obj1 [label="{<value> 610.0|}" shape=Mrecord]; var1:name -> obj1:value var2:name -> obj1:value } ``` 現在不論是使用 `total` 或是 `payment` 都是指同一個物件, 也就是 `610.0` 這個浮點數物件。 ## 使用名稱來操作物件的方法 指派名稱後, 我們也可以使用名稱來操作物件的方法, 例如: ```python >>> payment.__int__() # 叫用 __int__ 方法 610 ``` 寫法完全一樣, 只要在物件名稱與方法之間加上 `.` 就可以了。 ## 檢查不同名稱所代表的是否為同一個物件 如果想要知道 2 個不同的名字是否代表同一個物件, 有兩種方法: 1. 使用 `id` 內建函式取得物件的**識別編號 (identity)**:每個物件都有自己獨一無二的識別編號, `id` 內建函式可以告訴我們特定物件的識別編號, 識別編號相同, 就表示是同一個物件, 例如: ```python= >>> id(total) 2631393158872 >>> id(payment) 2631393158872 >>> id(twenty_off_books) 2631393158920 ``` 你可以看到透過 `total` 或是 `payment` 名稱取得的物件識別編號是一樣的, 因此是同一個物件, 但是透過 `twenty_off_books` 取得的物件識別編號就不同, 表示是不同的物件。 2. 使用 `is` 運算器幫我們比較物件的識別編號, 例如: ```python= >>> total is payment True >>> total is twenty_off_books False ``` 第 2、4 列顯示的 `True` 與 `False` 是 Python 預先建立的物件, 屬於『**布林 (boolean)**』類別, 代表**對 (真/成立)**、**錯 (假/不成立)** 的概念。上述程式可以解讀為我們問『`total` 是不是 `payment`?』而 Python 回答『**是**』;但當我們問『`total` 是 `twenty_off_books` 嗎?』Python 就回答『**不是**』。 :::info :memo: `is` 運算器實際上就是比較 2 個物件的識別編號, 而不是比較 2 個物件的值, 例如以下 2 個浮點數物件雖然值相同, 但卻是不同的物件: ```python >>> a = 36.0 >>> b = 36.0 >>> a is b False >>> id(a) 2631393159040 >>> id(b) 2631393159016 ``` 要特別注意的是, 對整數物件來說, 相同整數值的物件都是同一個物件, 例如: ```python >>> c = 10 >>> d = 10 >>> c is d True >>> id(c) 1475964672 >>> id(d) 1475964672 ``` 這是因為 Python 內部對於整數與浮點數處理的差異, 對於同一個整數值, Python 只會產生單一個物件, 並且不斷重複使用, 減少重複建立物件的額外工作。 ::: ## 判斷兩個物件是不是同一類 上一章提到過內建函式 `type` 會傳回引數所屬類別的 type 物件, 由於同一種類別的物件都會對應到同一個 type 物件, 因此我們只要用 `is` 運算器就可以判斷兩個物件是不是同一類, 例如: ```python >>> a = 20 >>> type(a) is type(30) True >>> type(a) is type(1.4) False >>> ``` ## 解除名稱與物件的綁定關係 你也可以刪除名稱, 刪除名稱就相當於把名牌從物件上取走丟到垃圾桶, 例如: ```python >>> del payment ``` ```graphviz digraph object { var1 [label="<name>╳payment" shape=record]; obj1 [label="{<value> 610.0|}" shape=Mrecord]; var1:name ->obj1:value [color=Red, style=dashed label="╳ 解除綁定"] } ``` 使用 `del` 敘述刪除名稱後, 如果再鍵入已經被刪除的名稱, 就會引發錯誤: ```python >>> payment Traceback (most recent call last): File "<pyshell#34>", line 1, in <module> payment NameError: name 'payment' is not defined >>> ``` 上面的錯誤訊息只要先看最後一列即可, 它告訴你發生了 `NameError` 這種類型的錯誤, 錯誤的內容是 `payment` 名稱**並未定義**, 這就是因為剛剛把 `payment` 名稱刪除了。 另外一種解除名稱與對應物件關係的方式, 就是將名稱指派給另一個物件, 相當於把名牌取走綁到另一個物件上。例如: ```python >>> payment = 732 >>> payment 732 ``` ```graphviz digraph object { var1 [label="<name> payment" shape=record]; obj1 [label="{<value> 610.0|}" shape=Mrecord]; obj2 [label="{<value> 732|}" shape=Mrecord]; var1:name ->obj2:value [label=" 重新指派"] var1:name ->obj1:value [color=Red, style=dashed label="╳ 解除繫結"] } ``` 當某個物件已經不再有名稱與之對應時, 就會列入廢棄的物件清單, Python 會在適當的時機回收廢棄物件所佔用的空間。當你確認某個名稱已經不再需要時, 使用 `del` 移除名稱是個好習慣, 可以避免程式被大量物件佔用空間導致無法建立新物件的狀況。