9. Class（類別）
****************

Class 提供了一種結合資料與功能的手段。建立一個 class 將會新增一個物件
的*型別 (type)*，並且允許建立該型別的新*實例 (instance)*。每一個 class
實例可以擁有一些維持該實例狀態的屬性 (attribute)。Class 實例也可以有一
些（由其 class 所定義的）method（方法），用於修改該實例的狀態。

與其他程式語言相比，Python 的 class 機制為 class 增加了最少的新語法跟
語意。他混合了 C++ 和 Modula-3 的 class 機制。Python 的 class 提供了所
有物件導向程式設計 (Object Oriented Programming) 的標準特色：class 繼
承機制允許多個 base class（基底類別），一個 derived class（衍生類別）
可以覆寫 (override) 其 base class 的任何 method，且一個 method 可以用
相同的名稱呼叫其 base class 的 method。物件可以包含任意數量及任意種類
的資料。如同模組一樣，class 也具有 Python 的動態特性：他們在執行期
(runtime) 被建立，且可以在建立之後被修改。

在 C++ 的術語中，class 成員（包含資料成員）通常都是*公開*的（除了以下
內容：私有變數），而所有的成員函式都是*虛擬*的。如同在 Modula-3 中一樣
，Python 並沒有提供簡寫可以從物件的 method 裡參照其成員：method 函式與
一個外顯的 (explicit)、第一個代表物件的引數被宣告，而此引數是在呼叫時
隱性地 (implicitly) 被提供。如同在 Smalltak 中，class 都是物件，這為
import 及重新命名提供了語意。不像 C++ 和 Modula-3，Pyhon 內建的型別可
以被使用者以 base class 用於其他擴充 (extension)。另外，如同在 C++ 中
，大多數有著特別語法的內建運算子（算術運算子、下標等）都可以為了 class
實例而被重新定義。

（由於缺乏普遍能接受的術語來討論 class，我偶爾會使用 Smalltalk 和 C++
的術語。我會使用 Modula-3 的術語，因為它比 C++ 更接近 Python 的物件導
向語意，但我預期比較少的讀者會聽過它。）


9.1. 關於名稱與物件的一段話
===========================

物件有個體性 (individuality)，且多個名稱（在多個作用域 (scope) ）可以
被連結到相同的物件。這在其他語言中被稱為別名 (aliasing)。初次接觸
Python 時通常不會注意這件事，而在處理不可變的基本型別（數值、字串、
tuple）時，它也可以安全地被忽略。然而，別名在含有可變物件（如 list（串
列）、dictionary（字典）、和大多數其他的型別）的 Python 程式碼語意中，
可能會有意外的效果。這通常有利於程式，因為別名在某些方面表現得像指標
(pointer)。舉例來說，在實作時傳遞一個物件是便宜的，因為只有指標被傳遞
；假如函式修改了一個作為引數傳遞的物件，呼叫函式者 (caller) 能夠見到這
些改變——這消除了在 Pascal 中兩個相異引數傳遞機制的需求。


9.2. Python 作用域 (Scope) 及命名空間 (Namespace)
=================================================

在介紹 class 之前，我必須先告訴你一些關於 Python 作用域的規則。Class
definition（類別定義）以命名空間展現了一些俐落的技巧，而你需要了解作用
域和命名空間的運作才能完整理解正在發生的事情。順帶一提，關於這個主題的
知識對任何進階的 Python 程式設計師都是很有用的。

讓我們從一些定義開始。

*命名空間*是從名稱到物件的映射。大部分的命名空間現在都是以 Python 的
dictionary 被實作，但通常不會以任何方式被察覺（除了性能），且它可能會
在未來改變。命名空間的例子有：內建名稱的集合（包含如 "abs()" 的函式，
和內建的例外名稱）；模組中的全域 (global) 名稱；和在函式調用中的區域
(local) 名稱。某種意義上，物件中的屬性集合也會形成一個命名空間。關於命
名空間的重要一點是，不同命名空間中的名稱之間絕對沒有關係；舉例來說，兩
個不一樣的模組都可以定義一個 "maximize" 函式而不會混淆——模組的使用者必
須為它加上前綴 (prefix) 模組名稱。

順帶一提，我使用*屬性 (attribute)* 這個字，統稱句號 (dot) 後面的任何名
稱——例如，運算式中的 "z.real"，"real" 是物件 "z" 的一個屬性。嚴格來說
，模組中名稱的參照都是屬性參照：在運算式 "modname.funcname" 中，
"modname" 是模組物件而 "funcname" 是它的屬性。在這種情況下，模組的屬性
和模組中定義的全域名稱碰巧有一個直接的對映：他們共享了相同的命名空間！
[1]

屬性可以是唯讀的或可寫的。在後者的情況下，對屬性的賦值是可能的。模組屬
性是可寫的：你可以寫 "modname.the_answer = 42"。可寫屬性也可以用 "del"
陳述式刪除。例如，"del modname.the_answer" 將從名為 "modname" 的物件中
刪除屬性 "the_answer"。

命名空間在不同的時刻被建立，並且有不同的壽命。當 Python 直譯器啟動時，
含有內建名稱的命名空間會被建立，並且永遠不會被刪除。當模組定義被讀入時
，模組的全域命名空間會被建立；一般情況下，模組的命名空間也會持續到直譯
器結束。被直譯器的頂層調用 (top-level invocation) 執行的陳述式，不論是
從腳本檔案讀取的或是互動模式中的，會被視為一個稱為 "__main__" 的模組的
一部分，因此它們具有自己的全域命名空間。（內建名稱實際上也存在一個模組
中，它被稱為 "builtins"。）

函式的區域命名空間是在呼叫函式時建立的，而當函式返回，或引發了未在函式
中處理的例外時，此命名空間將會被刪除。（實際上，忘記是描述實際發生的事
情的更好方法。） 當然，每個遞迴調用 (recursive invocation) 都有自己的
區域命名空間。

*作用域*是 Python 程式中的一個文本區域 (textual region)，在此區域，命
名空間是可直接存取的。這裡的「可直接存取的」意思是，對一個名稱的非限定
參照 (unqualified reference) 可以在命名空間內嘗試尋找該名稱。

儘管作用域是靜態地被決定，但它們是動態地被使用的。在執行期間內的任何時
間點，都會有 3 或 4 個巢狀的作用域，其命名空間是可以被直接存取的：

* 最內層作用域，會最先被搜尋，而它包含了區域名稱

* 那些外层闭包函数的作用域，包含“非局部、非全局”的名称，从最靠内层的那
  个作用域开始，逐层向外搜索。

* 倒數第二個作用域，包含當前模組的全域名稱

* 最外面的作用域（最後搜尋），是包含內建名稱的命名空間

如果一个名称被声明为全局，则所有引用和赋值都将直接指向“倒数第二层作用
域”，即包含模块的全局名称的作用域。 要重新绑定在最内层作用域以外找到的
变量，可以使用 "nonlocal" 语句；如果未使用 nonlocal 声明，这些变量将为
只读（尝试写入这样的变量将在最内层作用域中创建一个 *新的* 局部变量，而
使得同名的外部变量保持不变）。

通常，區域作用域會參照（文本的）當前函式的區域名稱。在函式外部，區域作
用域與全域作用域參照相同的命名空間：模組的命名空間。然而，Class
definition 會在區域作用域中放置另一個命名空間。

務必要了解，作用域是按文本被決定的：在模組中定義的函式，其全域作用域便
是該模組的命名空間，無論函式是從何處或以什麼別名被呼叫。另一方面，對名
稱的實際搜尋是在執行時期 (run time) 動態完成的——但是，語言定義的發展，
正朝向在「編譯」時期 (compile time) 的靜態名稱解析 (static name
resolution)，所以不要太依賴動態名稱解析 (dynamic name resolution)！ （
事實上，局部變數已經是靜態地被決定。）

一個 Python 的特殊癖好是——假如沒有 "global" 或 "nonlocal" 陳述式的效果
——名稱的賦值 (assignment) 都會指向最內層作用域。賦值不會複製資料——它們
只會把名稱連結至物件。刪除也是一樣：陳述式 "del x" 會從區域作用域參照
的命名空間移除 "x" 的連結。事實上，引入新名稱的所有運算都使用區域作用
域：特別是 "import" 陳述式和函式定義，會連結區域作用域內的模組或函式名
稱。

"global" 陳述式可以用來表示特定變數存活在全域作用域，應該被重新綁定到
那裡；"nonlocal" 陳述式表示特定變數存活在外圍作用域內，應該被重新綁定
到那裡。


9.2.1. 作用域和命名空間的範例
-----------------------------

這是一個範例，演示如何參照不同的作用域和命名空間，以及 "global" 和
"nonlocal" 如何影響變數的綁定：

   def scope_test():
       def do_local():
           spam = "local spam"

       def do_nonlocal():
           nonlocal spam
           spam = "nonlocal spam"

       def do_global():
           global spam
           spam = "global spam"

       spam = "test spam"
       do_local()
       print("After local assignment:", spam)
       do_nonlocal()
       print("After nonlocal assignment:", spam)
       do_global()
       print("After global assignment:", spam)

   scope_test()
   print("In global scope:", spam)

範例程式碼的輸出是：

   After local assignment: test spam
   After nonlocal assignment: nonlocal spam
   After global assignment: nonlocal spam
   In global scope: global spam

請注意，*區域*賦值（預設情況）不會改變 *scope_test* 對 *spam* 的連結。
"nonlocal" 賦值改變了 *scope_test* 對 *spam* 的連結，而 "global" 賦值
改變了模組層次的連結。

你還可以發現，在 "global" 賦值之前，沒有對 *spam* 的連結。


9.3. 初見 class
===============

Class 採用一些新的語法，三個新的物件型別，以及一些新的語意。


9.3.1. Class definition（類別定義）語法
---------------------------------------

Class definition 最簡單的形式如下：

   class ClassName:
       <statement-1>
       .
       .
       .
       <statement-N>

Class definition，如同函式定義（"def" 陳述式），必須在它們有任何效果前
先執行。（你可以想像把 class definition 放在一個 "if" 陳述式的分支，或
在函式裡。）

在實作時，class definition 內的陳述式通常會是函式定義，但其他陳述式也
是允許的，有時很有用——我們稍後會回到這裡。Class 中的函式定義通常會有一
個獨特的引數列表形式，取決於 method 的呼叫慣例——再一次地，這將會在稍後
解釋。

當進入 class definition，一個新的命名空間將會被建立，並且作為區域作用
域——因此，所有區域變數的賦值將進入這個新的命名空間。特別是，函式定義會
在這裡連結新函式的名稱。

正常地（從結尾處）離開 class definition 時，一個 *class 物件*會被建立
。基本上這是一個包裝器 (wrapper)，裝著 class definition 建立的命名空間
內容；我們將在下一節中更加了解 class 物件。原始的區域作用域（在進入
class definition 之前已生效的作用域）會恢復，在此 class 物件會被連結到
class definition 標頭中給出的 class 名稱（在範例中為 "ClassName"）。


9.3.2. Class 物件
-----------------

Class 物件支援兩種運算：屬性參照 (attribute reference) 和實例化
(instantiation)。

*屬性參照*使用 Python 中所有屬性參照的標準語法："obj.name"。有效的屬性
名稱是 class 物件被建立時，class 的命名空間中所有的名稱。所以，如果
class definition 看起來像這樣：

   class MyClass:
       """A simple example class"""
       i = 12345

       def f(self):
           return 'hello world'

那麼 "MyClass.i" 和 "MyClass.f" 都是有效的屬性參照，會分別回傳一個整數
和一個函式物件。Class 屬性也可以被指派 (assign)，所以您可以透過賦值改
變 "MyClass.i" 的值。"__doc__" 也是一個有效的屬性，會回傳屬於該 class
的說明字串 (docstring)：""A simple example class""。

Class *實例化*使用了函式記法 (function notation)。就好像 class 物件是
一個沒有參數的函式，它回傳一個新的 class 實例。例如（假設是上述的
class）：

   x = MyClass()

建立 class 的一個新*實例*，並將此物件指派給區域變數 "x"。

實例化運算（「呼叫」一個 class 物件）會建立一個空的物件。許多 class 喜
歡在建立物件時有著自訂的特定實例初始狀態。因此，class 可以定義一個名為
"__init__()" 的特別 method，像這樣：

   def __init__(self):
       self.data = []

當 class 定義了 "__init__()" method，class 實例化會為新建的 class 實例
自動調用 "__init__()"。所以在這個範例中，一個新的、初始化的實例可以如
此獲得：

   x = MyClass()

當然，"__init__()" method 可能為了更多的彈性而有引數。在這種情況下，要
給 class 實例化運算子的引數會被傳遞給 "__init__()"。例如：

   >>> class Complex:
   ...     def __init__(self, realpart, imagpart):
   ...         self.r = realpart
   ...         self.i = imagpart
   ...
   >>> x = Complex(3.0, -4.5)
   >>> x.r, x.i
   (3.0, -4.5)


9.3.3. 實例物件
---------------

現在，我們可以如何處理實例物件？實例物件能理解的唯一運算就是屬性參照。
有兩種有效的屬性名稱：資料屬性 (data attribute) 和 method。

*資料屬性*對應 Smalltalk 中的「實例變數」，以及 C++ 中的「資料成員」。
資料屬性不需要被宣告；和區域變數一樣，它們在第一次被賦值時就會立即存在
。例如，如果 "x" 是 "MyClass" 在上述例子中建立的實例，下面的程式碼將印
出值 "16"，而不留下蹤跡：

   x.counter = 1
   while x.counter < 10:
       x.counter = x.counter * 2
   print(x.counter)
   del x.counter

實例的另一種屬性參照是 *method*。Method 是一個「屬於」物件的函式。（在
Python 中，術語 method 並不是 class 實例所獨有的：其他物件型別也可以有
method。例如，list 物件具有稱為 append、insert、remove、sort 等 method
。但是，在下面的討論中，我們將用術語 method 來專門表示 class 實例物件
的 method，除非另有明確說明。）

實例物件的有效 method 名稱取決於其 class。根據定義，一個 class 中所有
的函式物件屬性，就定義了實例的對應 method。所以在我們的例子中，"x.f"
是一個有效的 method 參照，因為 "MyClass.f" 是一個函式，但 "x.i" 不是，
因為 "MyClass.i" 不是。但 "x.f" 與 "MyClass.f" 是不一樣的——它是一個
*method 物件*，而不是函式物件。


9.3.4. Method 物件
------------------

通常，一個 method 在它被連結後隨即被呼叫：

   x.f()

在 "MyClass" 的例子中，這將回傳字串 "’hello world’"。然而，並沒有必要
立即呼叫一個 method："x.f" 是一個 method 物件，並且可以被儲藏起來，之
後再被呼叫。舉例來說：

   xf = x.f
   while True:
       print(xf())

將會持續印出 "hello world" 直到天荒地老。

當一個 method 被呼叫時究竟會發生什麼事？你可能已經注意到 "x.f()" 被呼
叫時沒有任何的引數，儘管 "f()" 的函式定義有指定一個引數。這個引數發生
了什麼事？當一個需要引數的函式被呼叫而沒有給任何引數時，Python 肯定會
引發例外——即使該引數實際上沒有被使用...

事實上，你可能已經猜到了答案：method 的特殊之處在於，實例物件會作為函
式中的第一個引數被傳遞。在我們的例子中，"x.f()" 這個呼叫等同於
"MyClass.f(x)"。一般來說，呼叫一個有 *n* 個引數的 method，等同於呼叫一
個對應函式，其引數列表 (argument list) 被建立時，會在第一個引數前插入
該 method 的實例物件。

如果你仍然不了解 method 怎麼運作，看一眼實作可能會清楚一些事。當一個實
例的非資料屬性被參照時，該實例的 class 會被搜尋。如果該名稱是一個有效
的 class 屬性，而且是一個函式物件，則一個 method 物件會被建立，建立的
方法是藉由打包（指向）該實例物件及剛被找到的函式物件，形成一個抽象的物
件：這就是 method 物件。當 method 物件帶著一個引數列表被呼叫，則一個新
的引數列表會從實例物件和該引數列表被建構，而該函式物件會以這個新的引數
列表被呼叫。


9.3.5. Class 及實例變數
-----------------------

一般來說，實例變數用於每一個實例的獨特資料，而 class 變數用於該 class
的所有實例共享的屬性和 method：

   class Dog:

       kind = 'canine'         # class variable shared by all instances

       def __init__(self, name):
           self.name = name    # instance variable unique to each instance

   >>> d = Dog('Fido')
   >>> e = Dog('Buddy')
   >>> d.kind                  # shared by all dogs
   'canine'
   >>> e.kind                  # shared by all dogs
   'canine'
   >>> d.name                  # unique to d
   'Fido'
   >>> e.name                  # unique to e
   'Buddy'

如同在關於名稱與物件的一段話的討論，共享的資料若涉及 *mutable* 物件，
如 list 和 dictionary，可能會產生意外的影響。舉例來說，下列程式碼的
*tricks* list 不應該作為一個 class 變數使用，因為這個 list 將會被所有
的 *Dog* 實例所共享：

   class Dog:

       tricks = []             # mistaken use of a class variable

       def __init__(self, name):
           self.name = name

       def add_trick(self, trick):
           self.tricks.append(trick)

   >>> d = Dog('Fido')
   >>> e = Dog('Buddy')
   >>> d.add_trick('roll over')
   >>> e.add_trick('play dead')
   >>> d.tricks                # unexpectedly shared by all dogs
   ['roll over', 'play dead']

正確的 class 設計應該使用實例變數：

   class Dog:

       def __init__(self, name):
           self.name = name
           self.tricks = []    # creates a new empty list for each dog

       def add_trick(self, trick):
           self.tricks.append(trick)

   >>> d = Dog('Fido')
   >>> e = Dog('Buddy')
   >>> d.add_trick('roll over')
   >>> e.add_trick('play dead')
   >>> d.tricks
   ['roll over']
   >>> e.tricks
   ['play dead']


9.4. 隨意的備註
===============

如果屬性名稱同時出現在一個實例和一個 class 中，則屬性的尋找會以實例為
優先：

   >>> class Warehouse:
   ...    purpose = 'storage'
   ...    region = 'west'
   ...
   >>> w1 = Warehouse()
   >>> print(w1.purpose, w1.region)
   storage west
   >>> w2 = Warehouse()
   >>> w2.region = 'east'
   >>> print(w2.purpose, w2.region)
   storage east

資料屬性可能被 method 或是被物件的一般使用者（「客戶端」）所參照。也就
是說，class 不可用於實作純粹抽象的資料型別。事實上，在 Python 中沒有任
何可能的方法，可強制隱藏資料——這都是基於慣例。（另一方面，以 C 編寫的
Python 實作可以完全隱藏實作細節並且在必要時控制物件的存取；這可以被以
C 編寫的 Python 擴充所使用。）

客戶端應該小心使用資料屬性——客戶端可能會因為覆寫他們的資料屬性，而破壞
了被 method 維護的不變性。注意，客戶端可以增加他們自己的資料屬性到實例
物件，但不影響 method 的有效性，只要避免名稱衝突即可——再一次提醒，命名
慣例可以在這裡節省很多麻煩。

在 method 中參照資料屬性（或其他 method！）是沒有簡寫的。我發現這實際
上增加了 method 的可閱讀性：在瀏覽 method 時，絕不會混淆區域變數和實例
變數。

通常，方法的第一個引數稱為 "self"。這僅僅只是一個慣例："self" 這個名字
對 Python 來說完全沒有特別的意義。但請注意，如果不遵循慣例，你的程式碼
可能對其他 Python 程式設計師來說可讀性較低，此外，也可以想像一個可能因
信任此慣例而編寫的 *class 瀏覽器 (browser)* 程式。

任何一個作為 class 屬性的函式物件都為該 class 的實例定義了一個相應的
method。函式定義不一定要包含在 class definition 的文本中：將函式物件指
定給 class 中的區域變數也是可以的。例如：

   # Function defined outside the class
   def f1(self, x, y):
       return min(x, x+y)

   class C:
       f = f1

       def g(self):
           return 'hello world'

       h = g

現在 "f"、"g" 和 "h" 都是 class "C" 的屬性，並指向函式物件，所以他們都
是class "C" 實例的 method —— "h" 與 "g" 是完全一樣的。請注意，這種做法
通常只會使該程式的讀者感到困惑。

Method 可以藉由使用 "self" 引數的 method 屬性，呼叫其他 method：

   class Bag:
       def __init__(self):
           self.data = []

       def add(self, x):
           self.data.append(x)

       def addtwice(self, x):
           self.add(x)
           self.add(x)

Method 可以用與一般函式相同的方式參照全域名稱。與 method 相關的全域作
用域，就是包含其定義的模組。（class 永遠不會被用作全域作用域。）雖然人
們很少有在 method 中使用全域資料的充分理由，但全域作用域仍有許多合法的
使用：比方說，被 import 至全域作用域的函式和模組，可以被 method 以及在
該作用域中定義的函式和 class 所使用。通常，包含 method 的 class，它本
身就是被定義在這個全域作用域，在下一節，我們將看到 method 想要參照自己
的 class 的一些好原因。

每個值都是一個物件，因此都具有一個 *class*，也可以稱為它的 *type（型別
）*。它以 "object.__class__" 被儲存。


9.5. 繼承 (Inheritance)
=======================

當然，如果沒有支援繼承，「class」這個語言特色就不值得被稱為 class。一
個 derived class（衍生類別）定義的語法看起來如下：

   class DerivedClassName(BaseClassName):
       <statement-1>
       .
       .
       .
       <statement-N>

名稱 "BaseClassName" 被定義的作用域必須是包含 derived class 定義的作用
域。要代替 base class（基底類別）的名稱，用其他任意的運算式也是被允許
的。這會很有用，例如，當一個 base class 是在另一個模組中被定義時：

   class DerivedClassName(modname.BaseClassName):

執行 derived class 定義的過程，與執行 base class 相同。當 class 物件被
建構時，base class 會被記住。這是用於解析屬性參照：如果一個要求的屬性
無法在該 class 中找到，則會繼續在 base class 中搜尋。假如該 base class
本身也是衍生自其他 class，則這個規則會遞迴地被應用。

關於 derived class 的實例化並沒有特別之處："DerivedClassName()" 會建立
該 class 的一個新實例。Method 的參照被解析如下：對應的 class 屬性會被
搜尋，如果需要，沿著 base class 的繼承鍊往下走，如果這產生了一個函式物
件，則該 method 的參照是有效的。

Derived class 可以覆寫其 base class 的 method。因為 method 在呼叫同一
個物件的其他 method 時沒有特別的特權，所以當 base class 的一個 method
在呼叫相同 base class 中定義的另一個 method 時，最終可能會呼叫到一個覆
寫它的 derived class 中的 method。（給 C++ 程式設計師：Python 中所有
method 實際上都是 "virtual"。）

一個在 derived class 覆寫的 method 可能事實上是想要擴充而非單純取代
base class 中相同名稱的 method。要直接呼叫 base class 的 method 有一個
簡單的方法：只要呼叫 "BaseClassName.methodname(self, arguments)"。這有
時對客戶端也很有用。（請注意，只有在 base class 在全域作用域可以用
"BaseClassName" 被存取時，這方法才有效。）

Python 有兩個內建函式可以用於繼承：

* 使用 "isinstance()" 判斷一個實例的型別："isinstance(obj, int)" 只有
  在 "obj.__class__" 是 "int" 或衍伸自 "int" 時，結果才會是 "True"。

* 使用 "issubclass()" 判斷 class 繼承："issubclass(bool, int)" 會是
  "True"，因為 "bool" 是 "int" 的 subclass（子類別）。但是，
  "issubclass(float, int)" 是 "False"，因為 "float" 並不是 "int" 的
  subclass。


9.5.1. 多重繼承
---------------

Python 也支援多重繼承的形式。一個有多個 base class 的 class definition
看起來像這樣子：

   class DerivedClassName(Base1, Base2, Base3):
       <statement-1>
       .
       .
       .
       <statement-N>

在大多數情況下，最簡單的例子裡，你可以這樣思考，對於繼承自 parent
class（父類別）的屬性，其搜尋規則為：深度優先、從左到右、在階層裡重疊
的相同 class 中不重複搜尋。因此，假如有一個屬性在 "DerivedClassName"
沒有被找到，則在 "Base1" 搜尋它，接著（遞迴地）在 "Base1" 的 base
class 中搜尋，假如在那裡又沒有找到的話，會在 "Base2" 搜尋，依此類推。

事實上，它稍微複雜一些；method 的解析順序是動態地變化，以支援對
"super()" 的合作呼叫。這個方式在其他的多重繼承語言中，稱為呼叫下一個方
法 (call-next-method)，且比在單一繼承語言中的 super call（超級呼叫）來
得更強大。

動態排序是必要的，因為多重繼承的所有情況都表現一或多的菱形關係（其中至
少一個 parent class 可以從最底層 class 透過多個路徑存取）。例如，所有
的 class 都繼承自 "object"，因此任何多重繼承的情況都提供了多個到達
"object" 的路徑。為了避免 base class 被多次存取，動態演算法以這些方式
將搜尋順序線性化 (linearize)：保留每個 class 中規定的從左到右的順序、
對每個 parent 只會呼叫一次、使用單調的 (monotonic) 方式（意思是，一個
class 可以被 subclassed（子類別化），而不會影響其 parent 的搜尋優先順
序）。總之，這些特性使設計出可靠又可擴充、具有多重繼承的 class 成為可
能。更多資訊，請見 https://www.python.org/download/releases/2.3/mro/。


9.6. 私有變數
=============

「私有」(private) 實例變數，指的是不在物件內部便無法存取的變數，這在
Python 中是不存在的。但是，大多數 Python 的程式碼都遵守一個慣例：前綴
為一個底線的名稱（如："_spam"）應被視為 API （應用程式介面）的非公有
(non-public) 部分（無論它是函式、方法或是資料成員）。這被視為一個實作
細節，如有調整，亦不另行通知。

既然 class 私有的成員已有一個有效的用例（即避免名稱與 subclass 定義的
名稱衝突），這種機制也存在另一個有限的支援，稱為 *name mangling*（名稱
修飾）。任何格式為 "__spam"（至少兩個前導下底線，最多一個尾隨下底線）
的物件名稱 (identifier) 會被文本地被替換為 "_classname__spam"，在此
"classname" 就是去掉前導下底線的當前 class 名稱。只要這個修飾是在
class 的定義之中發生，它就會在不考慮該物件名稱的語法位置的情況下完成。

名稱修飾對於讓 subclass 覆寫 method 而不用破壞 class 內部的 method 呼
叫，是有幫助的。舉例來說：

   class Mapping:
       def __init__(self, iterable):
           self.items_list = []
           self.__update(iterable)

       def update(self, iterable):
           for item in iterable:
               self.items_list.append(item)

       __update = update   # private copy of original update() method

   class MappingSubclass(Mapping):

       def update(self, keys, values):
           # provides new signature for update()
           # but does not break __init__()
           for item in zip(keys, values):
               self.items_list.append(item)

在上例中，就算在 "MappingSubclass" 當中加入 "__update" 識別符，也能順
利運作，因為在 "Mapping" class 中，它會被替換為 "_Mapping__update"，而
在 "MappingSubclass" class 中，它會被替換為 "_MappingSubclass__update"
。

請注意，修飾規則是被設計來避免意外；它仍可能存取或修改一個被視為私有的
變數。這在特殊情況下甚至可能很有用，例如在除錯器 (debugger)。

另外也注意，傳遞給 "exec()" 或 "eval()" 的程式碼不會把調用 class 的名
稱視為當前的 class；這和 "global" 陳述式的效果類似，該效果同樣僅限於整
體被位元組編譯後 (byte-compiled) 的程式碼。同樣的限制適用於
"getattr()"，"setattr()" 和 "delattr()"，以及直接參照 "__dict__" 時。


9.7. 補充說明
=============

有时具有类似于 Pascal "record" 或 C "struct" 的数据类型是很有用的，将
一些带名称的数据项捆绑在一起。 实现这一目标的理想方式是使用
"dataclasses":

   from dataclasses import dataclass

   @dataclass
   class Employee:
       name: str
       dept: str
       salary: int

   >>> john = Employee('john', 'computer lab', 1000)
   >>> john.dept
   'computer lab'
   >>> john.salary
   1000

用來處理特殊抽象資料型別的一段 Python 程式碼，經常能以傳遞一個 class
來替代，此 class 模擬該資料型別的多種 method。例如，如果你有一個函式，
它會從一個檔案物件來格式化某些資料，你也可以定義一個有 "read()" 和
"readline()" method 的 class 作為替代方式，從字串緩衝區取得資料，並將
其作為引數來傳遞。

實例的 method 物件也具有屬性："m.__self__" 就是帶有 method "m()" 的實
例物件，而 "m.__func__" 則是該 method 所對應的函式物件。


9.8. 疊代器 (Iterator)
======================

到目前為止，你可能已經注意到大多數的容器 (container) 物件都可以使用
"for" 陳述式來進行迴圈：

   for element in [1, 2, 3]:
       print(element)
   for element in (1, 2, 3):
       print(element)
   for key in {'one':1, 'two':2}:
       print(key)
   for char in "123":
       print(char)
   for line in open("myfile.txt"):
       print(line, end='')

這種存取風格清晰、簡潔且方便。疊代器的使用在 Python 中處處可見且用法一
致。在幕後，"for" 陳述式會在容器物件上呼叫 "iter()"。該函式回傳一個疊
代器物件，此物件定義了 "__next__()" method，而此 method 會逐一存取容器
中的元素。當元素用盡時，"__next__()" 將引發 "StopIteration" 例外，來通
知 "for" 終止迴圈。你可以使用內建函式 "next()" 來呼叫 "__next__()"
method；這個例子展示了它的運作方式：

   >>> s = 'abc'
   >>> it = iter(s)
   >>> it
   <str_iterator object at 0x10c90e650>
   >>> next(it)
   'a'
   >>> next(it)
   'b'
   >>> next(it)
   'c'
   >>> next(it)
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
       next(it)
   StopIteration

看過疊代器協定的幕後機制後，在你的 class 加入疊代器的行為就很容易了。
定義一個 "__iter__()" method 來回傳一個帶有 "__next__()" method 的物件
。如果 class 已定義了 "__next__()"，則 "__iter__()" 可以只回傳 "self"
：

   class Reverse:
       """Iterator for looping over a sequence backwards."""
       def __init__(self, data):
           self.data = data
           self.index = len(data)

       def __iter__(self):
           return self

       def __next__(self):
           if self.index == 0:
               raise StopIteration
           self.index = self.index - 1
           return self.data[self.index]

   >>> rev = Reverse('spam')
   >>> iter(rev)
   <__main__.Reverse object at 0x00A1DB50>
   >>> for char in rev:
   ...     print(char)
   ...
   m
   a
   p
   s


9.9. 產生器 (Generator)
=======================

*產生器*是一個用於建立疊代器的簡單而強大的工具。它們的寫法和常規的函式
一樣，但當它們要回傳資料時，會使用 "yield" 陳述式。每次在產生器上呼叫
"next()" 時，它會從上次離開的位置恢復執行（它會記得所有資料值以及上一
個被執行的陳述式）。以下範例顯示，建立產生器可以相當地容易：

   def reverse(data):
       for index in range(len(data)-1, -1, -1):
           yield data[index]

   >>> for char in reverse('golf'):
   ...     print(char)
   ...
   f
   l
   o
   g

任何可以用產生器來完成的事，也能用以 class 為基礎的疊代器來完成，如同
前一節的描述。而讓產生器的程式碼更為精簡的原因是，"__iter__()" 和
"__next__()" method 會自動被建立。

另一個關鍵的特性在於，區域變數和執行狀態會在每次呼叫之間自動被儲存。這
使得該函式比使用 "self.index" 和 "self.data" 這種實例變數的方式更容易
編寫且更為清晰。

除了會自動建立 method 和儲存程式狀態，當產生器終止時，它們還會自動引發
"StopIteration"。這些特性結合在一起，使建立疊代器能與編寫常規函式一樣
容易。


9.10. 產生器運算式
==================

某些簡單的產生器可以寫成如運算式一般的簡潔程式碼，所用的語法類似 list
comprehension（串列綜合運算），但外層為括號而非方括號。這種運算式被設
計用於產生器將立即被外圍函式 (enclosing function) 所使用的情況。產生器
運算式與完整的產生器定義相比，程式碼較精簡但功能較少，也比等效的 list
comprehension 更為節省記憶體。

例如：

   >>> sum(i*i for i in range(10))                 # sum of squares
   285

   >>> xvec = [10, 20, 30]
   >>> yvec = [7, 5, 3]
   >>> sum(x*y for x,y in zip(xvec, yvec))         # dot product
   260

   >>> unique_words = set(word for line in page  for word in line.split())

   >>> valedictorian = max((student.gpa, student.name) for student in graduates)

   >>> data = 'golf'
   >>> list(data[i] for i in range(len(data)-1, -1, -1))
   ['f', 'l', 'o', 'g']

-[ 註解 ]-

[1] 有一個例外。模組物件有一個秘密的唯讀屬性，稱為 "__dict__"，它回傳
    用於實作模組命名空間的 dictionary；"__dict__" 這個名稱是一個屬性但
    不是全域名稱。顯然，使用此屬性將違反命名空間實作的抽象化，而應該僅
    限用於事後除錯器 (post-mortem debugger) 之類的東西。
