###### 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` 移除名稱是個好習慣, 可以避免程式被大量物件佔用空間導致無法建立新物件的狀況。