# Chapter B 特殊方法名稱
> " My specialty is being right when other people are wrong. "
> — [George Bernard Shaw](http://en.wikiquote.org/wiki/George_Bernard_Shaw)
## 深入
在本書其它幾處,我們已經見識過一些特殊方法——即在使用某些語法時 Python 所調用的“神奇”方法。使用特殊方法,類用起來如同序列、字典、函數、迭代器,或甚至像個數字!本附錄為我們已經見過特殊方法提供了參考,并對一些更加深奧的特殊方法進行了簡要介紹。
## 基礎知識
如果曾閱讀 [《類的簡介》](iterators.html#divingin)一章,你可能已經見識過了最常見的特殊方法: `__init__()` 方法。蓋章結束時,我寫的類多數需要進行一些初始化工作。還有一些其它的基礎特殊方法對調試自定義類也特別有用。
| 序號 | 目的 | 所編寫代碼 | Python 實際調用 |
| --- | --- | --- | --- |
| ① | 初始化一個實例 | `x = MyClass()` | [`x.__init__()`](http://docs.python.org/3.1/reference/datamodel.html#object.__init__) |
| ② | 字符串的“官方”表現形式 | `repr(x)` | [`x.__repr__()`](http://docs.python.org/3.1/reference/datamodel.html#object.__repr__) |
| ③ | 字符串的“非正式”值 | [`str(x)`](http://docs.python.org/3.1/reference/datamodel.html#object.__str__) | `x.__str__()` |
| ④ | 字節數組的“非正式”值 | `bytes(x)` | `x.__bytes__()` |
| ⑤ | 格式化字符串的值 | `format(x, `format_spec`)` | [`x.__format__(`format_spec`)`](http://docs.python.org/3.1/reference/datamodel.html#object.__format__) |
1. 對 `__init__()` 方法的調用發生在實例被創建 _之后_ 。如果要控制實際創建進程,請使用 [`__new__()` 方法](#esoterica)。
2. 按照約定, `__repr__()` 方法所返回的字符串為合法的 Python 表達式。
3. 在調用 `print(x)` 的同時也調用了 `__str__()` 方法。
4. 由于 `bytes` 類型的引入而_從 Python 3 開始出現_。
5. 按照約定,`format_spec` 應當遵循 [迷你語言格式規范【Format Specification Mini-Language】](http://www.python.org/doc/3.1/library/string.html#formatspec)。Python 標準類庫中的 `decimal.py` 提供了自己的 `__format__()` 方法。
## 行為方式與迭代器類似的類
在 [《迭代器》一章中](iterators.html),我們已經學習了如何使用 `__iter__()` 和 `__next__()` 方法從零開始創建迭代器。
| 序號 | 目的 | 所編寫代碼 | Python 實際調用 |
| --- | --- | --- | --- |
| ① | 遍歷某個序列 | `iter(seq)` | [`seq.__iter__()`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__iter__) |
| ② | 從迭代器中獲取下一個值 | `next(seq)` | [`seq.__next__()`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__next__) |
| ③ | 按逆序創建一個迭代器 | `reversed(seq)` | [`seq.__reversed__()`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__reversed__) |
1. 無論何時創建迭代器都將調用 `__iter__()` 方法。這是用初始值對迭代器進行初始化的絕佳之處。
2. 無論何時從迭代器中獲取下一個值都將調用 `__next__()` 方法。
3. `__reversed__()` 方法并不常用。它以一個現有序列為參數,并將該序列中所有元素從尾到頭以逆序排列生成一個新的迭代器。
正如我們在 [《迭代器》一章](iterators.html#a-fibonacci-iterator)中看到的,`for` 循環也可用作迭代器。在下面的循環中:
```
for x in seq:
print(x)
```
Python 3 將會調用 `seq.__iter__()` 以創建一個迭代器,然后對迭代器調用 `__next__()` 方法以獲取 `x` 的每個值。當 `__next__()` 方法引發 `StopIteration` 例外時, `for` 循環正常結束。
## 計算屬性
| 序號 | 目的 | 所編寫代碼 | Python 實際調用 |
| --- | --- | --- | --- |
| ① | 獲取一個計算屬性(無條件的) | `x.my_property` | [`x.__getattribute__(`'my_property'`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__getattribute__) |
| --- | --- | --- | --- |
| ② | 獲取一個計算屬性(后備) | `x.my_property` | [`x.__getattr__(`'my_property'`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__getattr__) |
| --- | --- | --- | --- |
| ③ | 設置某屬性 | `x.my_property = value` | [`x.__setattr__(`'my_property'`, `value`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__setattr__) |
| --- | --- | --- | --- |
| ④ | 刪除某屬性 | `del x.my_property` | [`x.__delattr__(`'my_property'`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__delattr__) |
| --- | --- | --- | --- |
| ⑤ | 列出所有屬性和方法 | `dir(x)` | [`x.__dir__()`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__dir__) |
| --- | --- | --- | --- |
1. 如果某個類定義了 `__getattribute__()` 方法,在 _每次引用屬性或方法名稱時_ Python 都調用它(特殊方法名稱除外,因為那樣將會導致討厭的無限循環)。
2. 如果某個類定義了 `__getattr__()` 方法,Python 將只在正常的位置查詢屬性時才會調用它。如果實例 `x` 定義了屬性 `color`, `x.color` 將 _不會_ 調用 `x.__getattr__('color')`;而只會返回 `x.color` 已定義好的值。
3. 無論何時給屬性賦值,都會調用 `__setattr__()` 方法。
4. 無論何時刪除一個屬性,都將調用 `__delattr__()` 方法。
5. 如果定義了 `__getattr__()` 或 `__getattribute__()` 方法, `__dir__()` 方法將非常有用。通常,調用 `dir(x)` 將只顯示正常的屬性和方法。如果 `__getattr()__` 方法動態處理 `color` 屬性, `dir(x)` 將不會將 `color` 列為可用屬性。可通過覆蓋 `__dir__()` 方法允許將 `color` 列為可用屬性,對于想使用你的類但卻不想深入其內部的人來說,該方法非常有益。
`__getattr__()` 和 `__getattribute__()` 方法的區別非常細微,但非常重要。可以用兩個例子來解釋一下:
```
class Dynamo:
def __getattr__(self, key):
return 'PapayaWhip'
else:
>>> dyn = Dynamo()
'PapayaWhip'
>>> dyn.color = 'LemonChiffon'
'LemonChiffon'
```
1. 屬性名稱以字符串的形式傳入 `__getattr()__` 方法。如果名稱為 `'color'`,該方法返回一個值。(在此情況下,它只是一個硬編碼的字符串,但可以正常地進行某些計算并返回結果。)
2. 如果屬性名稱未知, `__getattr()__` 方法必須引發一個 `AttributeError` 例外,否則在訪問未定義屬性時,代碼將只會默默地失敗。(從技術角度而言,如果方法不引發例外或顯式地返回一個值,它將返回 `None` ——Python 的空值。這意味著 _所有_ 未顯式定義的屬性將為 `None`,幾乎可以肯定這不是你想看到的。)
3. `dyn` 實例沒有名為 `color` 的屬性,因此在提供計算值時將調用 `__getattr__()` 。
4. 在顯式地設置 `dyn.color` 之后,將不再為提供 `dyn.color` 的值而調用 `__getattr__()` 方法,因為 `dyn.color` 已在該實例中定義。
另一方面,`__getattribute__()` 方法是絕對的、無條件的。
```
class SuperDynamo:
def __getattribute__(self, key):
if key == 'color':
return 'PapayaWhip'
else:
raise AttributeError
>>> dyn = SuperDynamo()
'PapayaWhip'
>>> dyn.color = 'LemonChiffon'
'PapayaWhip'
```
1. 在獲取 `dyn.color` 的值時將調用 `__getattribute__()` 方法。
2. 即便已經顯式地設置 `dyn.color`,在獲取 `dyn.color` 的值時, _仍將調用_ `__getattribute__()` 方法。如果存在 `__getattribute__()` 方法,將在每次查找屬性和方法時 _無條件地調用_ 它,哪怕在創建實例之后已經顯式地設置了屬性。
> ? 如果定義了類的 `__getattribute__()` 方法,你可能還想定義一個 `__setattr__()` 方法,并在兩者之間進行協同,以跟蹤屬性的值。否則,在創建實例之后所設置的值將會消失在黑洞中。
必須特別小心 `__getattribute__()` 方法,因為 Python 在查找類的方法名稱時也將對其進行調用。
```
class Rastan:
def __getattribute__(self, key):
def swim(self):
pass
>>> hero = Rastan()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __getattribute__
AttributeError
```
1. 該類定義了一個總是引發 `AttributeError` 例外的 `__getattribute__()` 方法。沒有屬性或方法的查詢會成功。
2. 調用 `hero.swim()` 時,Python 將在 `Rastan` 類中查找 `swim()` 方法。該查找將執行整個 `__getattribute__()` 方法,因為所有的屬性和方法查找都通過 `__getattribute__()` 方法。在此例中, `__getattribute__()` 方法引發 `AttributeError` 例外,因此該方法查找過程將會失敗,而方法調用也將失敗。
## 行為方式與函數類似的類
可以讓類的實例變得可調用——就像函數可以調用一樣——通過定義 `__call__()` 方法。
| 序號 | 目的 | 所編寫代碼 | Python 實際調用 |
| --- | --- | --- | --- |
| | 像調用函數一樣“調用”一個實例 | `my_instance()` | [`my_instance.__call__()`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__call__) |
[`zipfile` 模塊](http://docs.python.org/3.1/library/zipfile.html) 通過該方式定義了一個可以使用給定密碼解密 經加密 zip 文件的類。該 zip 解密 算法需要在解密的過程中保存狀態。通過將解密器定義為類,使我們得以在 decryptor 類的單個實例中對該狀態進行維護。狀態在 `__init__()` 方法中進行初始化,如果文件 經加密 則進行更新。但由于該類像函數一樣“可調用”,因此可以將實例作為 `map()` 函數的第一個參數傳入,代碼如下:
```
# excerpt from zipfile.py
class _ZipDecrypter:
.
.
.
def __init__(self, pwd):
self.key1 = 591751049
self.key2 = 878082192
for p in pwd:
self._UpdateKeys(p)
assert isinstance(c, int)
k = self.key2 | 2
c = c ^ (((k * (k^1)) >> 8) & 255)
self._UpdateKeys(c)
return c
.
.
.
bytes = zef_file.read(12)
```
1. `_ZipDecryptor` 類維護了以三個旋轉密鑰形式出現的狀態,該狀態稍后將在 `_UpdateKeys()` 方法中更新(此處未展示)。
2. 該類定義了一個 `__call__()` 方法,使得該類可像函數一樣調用。在此例中,`__call__()` 對 zip 文件的單個字節進行解密,然后基于經解密的字節對旋轉密碼進行更新。
3. `zd` 是 `_ZipDecryptor` 類的一個實例。變量 `pwd` 被傳入 `__init__()` 方法,并在其中被存儲和用于首次旋轉密碼更新。
4. 給出 zip 文件的頭 12 個字節,將這些字節映射給 `zd` 進行解密,實際上這將導致調用 `__call__()` 方法 12 次,也就是 更新內部狀態并返回結果字節 12 次。
## 行為方式與序列類似的類
如果類作為一系列值的容器出現——也就是說如果對某個類來說,是否“包含”某值是件有意義的事情——那么它也許應該定義下面的特殊方法已,讓它的行為方式與序列類似。
| 序號 | 目的 | 所編寫代碼 | Python 實際調用 |
| --- | --- | --- | --- |
| | 序列的長度 | `len(seq)` | [`seq.__len__()`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__len__) |
| | 了解某序列是否包含特定的值 | `x in seq` | [`seq.__contains__(`x`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__contains__) |
[`cgi` 模塊](http://docs.python.org/3.1/library/cgi.html) 在其 `FieldStorage` 類中使用了這些方法,該類用于表示提交給動態網頁的所有表單字段或查詢參數。
```
# A script which responds to http://example.com/search?q=cgi
import cgi
fs = cgi.FieldStorage()
do_search()
# An excerpt from cgi.py that explains how that works
class FieldStorage:
.
.
.
if self.list is None:
raise TypeError('not indexable')
```
1. 一旦創建了 `cgi.FieldStorage` 類的實例,就可以使用 “`in`” 運算符來檢查查詢字符串中是否包含了某個特定參數。
2. 而 `__contains__()` 方法是令該魔法生效的主角。
3. 如果代碼為 `if 'q' in fs`,Python 將在 `fs` 對象中查找 `__contains__()` 方法,而該方法在 `cgi.py` 中已經定義。`'q'` 的值被當作 `key` 參數傳入 `__contains__()` 方法。
4. 同樣的 `FieldStorage` 類還支持返回其長度,因此可以編寫代碼 `len(`fs`)` 而其將調用 `FieldStorage` 的 `__len__()` 方法,并返回其識別的查詢參數個數。
5. `self.keys()` 方法檢查 `self.list is None` 是否為真值,因此 `__len__` 方法無需重復該錯誤檢查。
## 行為方式與字典類似的類
在前一節的基礎上稍作拓展,就不僅可以對 “`in`” 運算符和 `len()` 函數進行響應,還可像全功能字典一樣根據鍵來返回值。
| 序號 | 目的 | 所編寫代碼 | Python 實際調用 |
| --- | --- | --- | --- |
| | 通過鍵來獲取值 | `x[key]` | [`x.__getitem__(`key`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__getitem__) |
| | 通過鍵來設置值 | `x[key] = value` | [`x.__setitem__(`key`, `value`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__setitem__) |
| | 刪除一個鍵值對 | `del x[key]` | [`x.__delitem__(`key`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__delitem__) |
| | 為缺失鍵提供默認值 | `x[nonexistent_key]` | [`x.__missing__(`nonexistent_key`)`](http://docs.python.org/3.1/library/collections.html#collections.defaultdict.__missing__) |
[`cgi` 模塊](http://docs.python.org/3.1/library/cgi.html) 的 [`FieldStorage` 類](#acts-like-list-example) 同樣定義了這些特殊方法,也就是說可以像下面這樣編碼:
```
# A script which responds to http://example.com/search?q=cgi
import cgi
fs = cgi.FieldStorage()
if 'q' in fs:
# An excerpt from cgi.py that shows how it works
class FieldStorage:
.
.
.
if self.list is None:
raise TypeError('not indexable')
found = []
for item in self.list:
if item.name == key: found.append(item)
if not found:
raise KeyError(key)
if len(found) == 1:
return found[0]
else:
return found
```
1. `fs` 對象是 `cgi.FieldStorage` 類的一個實例,但仍然可以像 `fs['q']` 這樣估算表達式。
2. `fs['q']` 將 `key` 參數設置為 `'q'` 來調用 `__getitem__()` 方法。然后它將在其內部維護的查詢參數列表 (`self.list`) 中查找一個 `.name` 與給定鍵相符的字典項。
## 行為方式與數值類似的類
使用適當的特殊方法,可以將類的行為方式定義為與數字相仿。也就是說,可以進行相加、相減,并進行其它數學運算。這就是 分數 的實現方式—— `Fraction` 類實現了這些特殊方法,然后就可以進行下列運算了:
```
>>> from fractions import Fraction
>>> x = Fraction(1, 3)
>>> x / 3
Fraction(1, 9)
```
以下是實現“類數字”類的完整特殊方法清單:
| 序號 | 目的 | 所編寫代碼 | Python 實際調用 |
| --- | --- | --- | --- |
| | 加法 | `x + y` | [`x.__add__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__add__) |
| | 減法 | `x - y` | [`x.__sub__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__sub__) |
| | 乘法 | `x * y` | [`x.__mul__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__mul__) |
| | 除法 | `x / y` | [`x.__truediv__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__truediv__) |
| | 地板除 | `x // y` | [`x.__floordiv__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__floordiv__) |
| | 取模(取余) | `x % y` | [`x.__mod__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__mod__) |
| | 地板除 _&_ 取模 | `divmod(x, y)` | [`x.__divmod__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__divmod__) |
| | 乘冪 | `x ** y` | [`x.__pow__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__pow__) |
| | 左位移 | `x << y` | [`x.__lshift__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__lshift__) |
| | 右位移 | `x >> y` | [`x.__rshift__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__rshift__) |
| | 按位 `and` | `x & y` | [`x.__and__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__and__) |
| | 按位 `xor` | `x ^ y` | [`x.__xor__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__xor__) |
| | 按位 `or` | `x | y` | [`x.__or__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__or__) |
如果 `x` 是某個實現了所有這些方法的類的實例,那么萬事大吉。但如果未實現其中之一呢?或者更糟,如果實現了,但卻無法處理某幾類參數會怎么樣?例如:
```
>>> from fractions import Fraction
>>> x = Fraction(1, 3)
>>> 1 / x
Fraction(3, 1)
```
這并 _不是_ 傳入一個 `分數` 并將其除以一個整數(如前例那樣)的情況。前例中的情況非常直觀: `x / 3` 調用 `x.__truediv__(3)`,而`Fraction` 的 `__truediv__()` 方法處理所有的數學運算。但整數并不“知道”如何對分數進行數學計算。因此本例該如何運作呢?
和 _反映操作_ 相關的還有第二部分算數特殊方法。給定一個二元算術運算 (_例如:_ `x / y`),有兩種方法來實現它:
1. 告訴 `x` 將自己除以 `y`,或者
2. 告訴 `y` 去除 `x`
之前提到的特殊方法集合采用了第一種方式:對于給定 `x / y`,它們為 `x` 提供了一種途徑來表述“我知道如何將自己除以 `y`。”下面的特殊方法集合采用了第二種方法:它們向 `y` 提供了一種途徑來表述“我知道如何成為分母,并用自己去除 `x`。”
| 序號 | 目的 | 所編寫代碼 | Python 實際調用 |
| --- | --- | --- | --- |
| | 加法 | `x + y` | [`y.__radd__(`x`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__radd__) |
| | 減法 | `x - y` | [`y.__rsub__(`x`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__rsub__) |
| | 乘法 | `x * y` | [`y.__rmul__(`x`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__rmul__) |
| | 除法 | `x / y` | [`y.__rtruediv__(`x`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__rtruediv__) |
| | 地板除 | `x // y` | [`y.__rfloordiv__(`x`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__rfloordiv__) |
| | 取模(取余) | `x % y` | [`y.__rmod__(`x`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__rmod__) |
| | 地板除 _&_ 取模 | `divmod(x, y)` | [`y.__rdivmod__(`x`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__rdivmod__) |
| | 乘冪 | `x ** y` | [`y.__rpow__(`x`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__rpow__) |
| | 左位移 | `x << y` | [`y.__rlshift__(`x`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__rlshift__) |
| | 右位移 | `x >> y` | [`y.__rrshift__(`x`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__rrshift__) |
| | 按位 `and` | `x & y` | [`y.__rand__(`x`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__rand__) |
| | 按位 `xor` | `x ^ y` | [`y.__rxor__(`x`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__rxor__) |
| | 按位 `or` | `x | y` | [`y.__ror__(`x`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__ror__) |
|
但是等一下!還有更多特殊方法!如果在進行“原地”操作,如: `x /= 3`,還可定義更多的特殊方法。
| 序號 | 目的 | 所編寫代碼 | Python 實際調用 |
| --- | --- | --- | --- |
| | 原地加法 | `x += y` | [`x.__iadd__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__iadd__) |
| | 原地減法 | `x -= y` | [`x.__isub__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__isub__) |
| | 原地乘法 | `x *= y` | [`x.__imul__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__imul__) |
| | 原地除法 | `x /= y` | [`x.__itruediv__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__itruediv__) |
| | 原地地板除法 | `x //= y` | [`x.__ifloordiv__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__ifloordiv__) |
| | 原地取模 | `x %= y` | [`x.__imod__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__imod__) |
| | 原地乘冪 | `x **= y` | [`x.__ipow__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__ipow__) |
| | 原地左位移 | `x <<= y` | [`x.__ilshift__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__ilshift__) |
| | 原地右位移 | `x >>= y` | [`x.__irshift__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__irshift__) |
| | 原地按位 `and` | `x &= y` | [`x.__iand__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__iand__) |
| | 原地按位 `xor` | `x ^= y` | [`x.__ixor__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__ixor__) |
| | 原地按位 `or` | `x |= y` | [`x.__ior__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__ior__) |
注意:多數情況下,并不需要原地操作方法。如果未對特定運算定義“就地”方法,Python 將會試著使用(普通)方法。例如,為執行表達式 `x /= y`,Python 將會:
1. 試著調用 `x.__itruediv__(`y`)`。如果該方法已經定義,并返回了 `NotImplemented` 之外的值,那已經大功告成了。
2. 試圖調用 `x.__truediv__(`y`)`。如果該方法已定義并返回一個 `NotImplemented` 之外的值, `x` 的舊值將被丟棄,并將所返回的值替代它,就像是進行了 `x = x / y` 運算。
3. 試圖調用 `y.__rtruediv__(`x`)`。如果該方法已定義并返回了一個 `NotImplemented` 之外的值,`x` 的舊值將被丟棄,并用所返回值進行替換。
因此如果想對原地運算進行優化,僅需像 `__itruediv__()` 方法一樣定義“原地”方法。否則,基本上 Python 將會重新生成原地運算公式,以使用常規的運算及變量賦值。
還有一些“一元”數學運算,可以對“類-數字”對象自己執行。
| 序號 | 目的 | 所編寫代碼 | Python 實際調用 |
| --- | --- | --- | --- |
| | 負數 | `-x` | [`x.__neg__()`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__neg__) |
| | 正數 | `+x` | [`x.__pos__()`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__pos__) |
| | 絕對值 | `abs(x)` | [`x.__abs__()`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__abs__) |
| | 取反 | `~x` | [`x.__invert__()`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__invert__) |
| | 復數 | `complex(x)` | [`x.__complex__()`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__complex__) |
| | 整數轉換 | `int(x)` | [`x.__int__()`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__int__) |
| | 浮點數 | `float(x)` | [`x.__float__()`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__float__) |
| | 四舍五入至最近的整數 | `round(x)` | [`x.__round__()`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__round__) |
| | 四舍五入至最近的 `n` 位小數 | `round(x, n)` | [`x.__round__(n)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__round__) |
| | `>= x` 的最小整數 | `math.ceil(x)` | [`x.__ceil__()`](http://docs.python.org/3.1/library/math.html#math.ceil) |
| | `<= x`的最大整數 | `math.floor(x)` | [`x.__floor__()`](http://docs.python.org/3.1/library/math.html#math.floor) |
| | 對 `x` 朝向 0 取整 | `math.trunc(x)` | [`x.__trunc__()`](http://docs.python.org/3.1/library/math.html#math.trunc) |
| [PEP 357](http://www.python.org/dev/peps/pep-0357/) | 作為列表索引的數字 | `a_list[x]` | [`a_list[x.__index__()]`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__index__) |
## 可比較的類
我將此內容從前一節中拿出來使其單獨成節,是因為“比較”操作并不局限于數字。許多數據類型都可以進行比較——字符串、列表,甚至字典。如果要創建自己的類,且對象之間的比較有意義,可以使用下面的特殊方法來實現比較。
| 序號 | 目的 | 所編寫代碼 | Python 實際調用 |
| --- | --- | --- | --- |
| | 相等 | `x == y` | [`x.__eq__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__eq__) |
| | 不相等 | `x != y` | [`x.__ne__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__ne__) |
| | 小于 | `x < y` | [`x.__lt__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__lt__) |
| | 小于或等于 | `x <= y` | [`x.__le__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__le__) |
| | 大于 | `x > y` | [`x.__gt__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__gt__) |
| | 大于或等于 | `x >= y` | [`x.__ge__(`y`)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__ge__) |
| | 布爾上上下文環境中的真值 | `if x:` | [`x.__bool__()`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__bool__) |
> ?如果定義了 `__lt__()` 方法但沒有定義 `__gt__()` 方法,Python 將通過經交換的算子調用 `__lt__()` 方法。然而,Python 并不會組合方法。例如,如果定義了 `__lt__()` 方法和 `__eq()__` 方法,并試圖測試是否 `x <= y`,Python 不會按順序調用 `__lt__()` 和 `__eq()__` 。它將只調用 `__le__()` 方法。
## 可序列化的類
Python 支持 [任意對象的序列化和反序列化](serializing.html)。(多數 Python 參考資料稱該過程為 “pickling” 和 “unpickling”)。該技術對與將狀態保存為文件并在稍后恢復它非常有意義。所有的 [內置數據類型](native-datatypes.html) 均已支持 pickling 。如果創建了自定義類,且希望它能夠 pickle,閱讀 [pickle 協議](http://docs.python.org/3.1/library/pickle.html) 了解下列特殊方法何時以及如何被調用。
| 序號 | 目的 | 所編寫代碼 | Python 實際調用 |
| --- | --- | --- | --- |
| | 自定義對象的復制 | `copy.copy(x)` | [`x.__copy__()`](http://docs.python.org/3.1/library/copy.html) |
| | 自定義對象的深度復制 | `copy.deepcopy(x)` | [`x.__deepcopy__()`](http://docs.python.org/3.1/library/copy.html) |
| | 在 pickling 之前獲取對象的狀態 | `pickle.dump(x, `file`)` | [`x.__getstate__()`](http://docs.python.org/3.1/library/pickle.html#pickle-state) |
| | 序列化某對象 | `pickle.dump(x, `file`)` | [`x.__reduce__()`](http://docs.python.org/3.1/library/pickle.html#pickling-class-instances) |
| | 序列化某對象(新 pickling 協議) | `pickle.dump(x, `file`, `protocol_version`)` | [`x.__reduce_ex__(`protocol_version`)`](http://docs.python.org/3.1/library/pickle.html#pickling-class-instances) |
| * | 控制 unpickling 過程中對象的創建方式 | `x = pickle.load(`file`)` | [`x.__getnewargs__()`](http://docs.python.org/3.1/library/pickle.html#pickling-class-instances) |
| * | 在 unpickling 之后還原對象的狀態 | `x = pickle.load(`file`)` | [`x.__setstate__()`](http://docs.python.org/3.1/library/pickle.html#pickle-state) |
* 要重建序列化對象,Python 需要創建一個和被序列化的對象看起來一樣的新對象,然后設置新對象的所有屬性。`__getnewargs__()` 方法控制新對象的創建過程,而 `__setstate__()` 方法控制屬性值的還原方式。
## 可在 `with` 語塊中使用的類
`with` 語塊定義了 [運行時刻上下文環境](http://www.python.org/doc/3.1/library/stdtypes.html#typecontextmanager);在執行 `with` 語句時將“進入”該上下文環境,而執行該語塊中的最后一條語句將“退出”該上下文環境。
| 序號 | 目的 | 所編寫代碼 | Python 實際調用 |
| --- | --- | --- | --- |
| | 在進入 `with` 語塊時進行一些特別操作 | `with x:` | [`x.__enter__()`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__enter__) |
| | 在退出 `with` 語塊時進行一些特別操作 | `with x:` | [`x.__exit__()`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__exit__) |
以下是 [`with `file`` 習慣用法](files.html#with) 的運作方式:
```
# excerpt from io.py:
def _checkClosed(self, msg=None):
'''Internal: raise an ValueError if file is closed
'''
if self.closed:
raise ValueError('I/O operation on closed file.'
if msg is None else msg)
def __enter__(self):
'''Context management protocol. Returns self.'''
def __exit__(self, *args):
'''Context management protocol. Calls close()'''
```
1. 該文件對象同時定義了一個 `__enter__()` 和一個 `__exit__()` 方法。該 `__enter__()` 方法檢查文件是否處于打開狀態;如果沒有, `_checkClosed()` 方法引發一個例外。
2. `__enter__()` 方法將始終返回 `self`?—— 這是 `with` 語塊將用于調用屬性和方法的對象
3. 在 `with` 語塊結束后,文件對象將自動關閉。怎么做到的?在 `__exit__()` 方法中調用了 `self.close()` .
> ?該 `__exit__()` 方法將總是被調用,哪怕是在 `with` 語塊中引發了例外。實際上,如果引發了例外,該例外信息將會被傳遞給 `__exit__()` 方法。查閱 [With 狀態上下文環境管理器](http://www.python.org/doc/3.1/reference/datamodel.html#with-statement-context-managers) 了解更多細節。
要了解關于上下文管理器的更多內容,請查閱 [《自動關閉文件》](files.html#with) 和 [《重定向標準輸出》](files.html#redirect)。
## 真正神奇的東西
如果知道自己在干什么,你幾乎可以完全控制類是如何比較的、屬性如何定義,以及類的子類是何種類型。
| 序號 | 目的 | 所編寫代碼 | Python 實際調用 |
| --- | --- | --- | --- |
| | 類構造器 | `x = MyClass()` | [`x.__new__()`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__new__) |
| * | 類析構器 | `del x` | [`x.__del__()`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__del__) |
| | 只定義特定集合的某些屬性 | [`x.__slots__()`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__slots__) |
| | 自定義散列值 | `hash(x)` | [`x.__hash__()`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__hash__) |
| | 獲取某個屬性的值 | `x.color` | [`type(x).__dict__['color'].__get__(x, type(x))`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__get__) |
| | 設置某個屬性的值 | `x.color = 'PapayaWhip'` | [`type(x).__dict__['color'].__set__(x, 'PapayaWhip')`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__set__) |
| | 刪除某個屬性 | `del x.color` | [`type(x).__dict__['color'].__del__(x)`](http://www.python.org/doc/3.1/reference/datamodel.html#object.__delete__) |
| | 控制某個對象是否是該對象的實例 your class | `isinstance(x, MyClass)` | [`MyClass.__instancecheck__(x)`](http://www.python.org/dev/peps/pep-3119/#overloading-isinstance-and-issubclass) |
| | 控制某個類是否是該類的子類 | `issubclass(C, MyClass)` | [`MyClass.__subclasscheck__(C)`](http://www.python.org/dev/peps/pep-3119/#overloading-isinstance-and-issubclass) |
| | 控制某個類是否是該抽象基類的子類 | `issubclass(C, MyABC)` | [`MyABC.__subclasshook__(C)`](http://docs.python.org/3.1/library/abc.html#abc.ABCMeta.__subclasshook__) |
\* 確切掌握 Python 何時調用 `__del__()` 特別方法 [是件難以置信的復雜](http://www.python.org/doc/3.1/reference/datamodel.html#object.__del__)事情。要想完全理解它,必須清楚 [Python 如何在內存中跟蹤對象](http://www.python.org/doc/3.1/reference/datamodel.html#objects-values-and-types)。以下有一篇好文章介紹 [Python 垃圾收集和類析構器](http://www.electricmonk.nl/log/2008/07/07/python-destructor-and-garbage-collection-notes/)。還可以閱讀 [《弱引用》](http://mindtrove.info/articles/python-weak-references/)、[《`weakref` 模塊》](http://docs.python.org/3.1/library/weakref.html),還可以將 [《`gc` 模塊》](http://www.python.org/doc/3.1/library/gc.html) 當作補充閱讀材料。
## 深入閱讀
本附錄中提到的模塊:
* [`zipfile` 模塊](http://docs.python.org/3.1/library/zipfile.html)
* [`cgi` 模塊](http://docs.python.org/3.1/library/cgi.html)
* [`collections` 模塊](http://www.python.org/doc/3.1/library/collections.html)
* [`math[數學]` 模塊](http://docs.python.org/3.1/library/math.html)
* [`pickle` 模塊](http://docs.python.org/3.1/library/pickle.html)
* [`copy` 模塊](http://docs.python.org/3.1/library/copy.html)
* [`abc` (“抽象基類”) 模塊](http://docs.python.org/3.1/library/abc.html)
其它啟發式閱讀:
* [迷你語言格式規范](http://www.python.org/doc/3.1/library/string.html#formatspec)
* [Python 數據模型](http://www.python.org/doc/3.1/reference/datamodel.html)
* [內建類型](http://www.python.org/doc/3.1/library/stdtypes.html)
* [PEP 357: 使任何對象可以使用切片](http://www.python.org/dev/peps/pep-0357/)
* [PEP 3119: 抽象基類簡介](http://www.python.org/dev/peps/pep-3119/)
- 版權信息
- Chapter -1 《深入 Python 3》中有何新內容
- Chapter 0 安裝 Python
- Chapter 1 你的第一個 Python 程序
- Chapter 2 內置數據類型
- Chapter 3 解析
- Chapter 4 字符串
- Chapter 5 正則表達式
- Chapter 6 閉合 與 生成器
- Chapter 7 類 & 迭代器
- Chapter 8 高級迭代器
- Chapter 9 單元測試
- Chapter 10 重構
- Chapter 11 文件
- Chapter 12 XML
- Chapter 13 序列化Python對象
- Chapter 14 HTTP Web 服務
- Chapter 15 案例研究:將chardet移植到Python 3
- Chapter 16 打包 Python 類庫
- Chapter A 使用2to3將代碼移植到Python 3
- Chapter B 特殊方法名稱
- Chapter C 接下來閱讀什么?