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 個巢狀的作用域，其命名空間是可以被直接存取的：

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

* 任何外圍函式 (enclosing function) 的作用域，會從最近的外圍作用域開始
  搜尋，它包含了非區域 (non-local) 和非全域 (non-global) 的名稱

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

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

如果一個名稱被宣告為全域，則所有的參照和賦值將直接轉到包含模組全域名稱
的倒數第二個作用域。要重新連結最內層作用域以外找到的變數，可以使用
"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，一個新的命名空間將會被建立，並且作為區域作用
域——因此，所有區域變數的賦值將進入這個新的命名空間。特別是，函式定義會
在這裡連結新函式的名稱。

当 (从结尾处) 正常离开类定义时，将创建一个 *类对象*。 这基本上是一个围
绕类定义所创建的命名空间的包装器；我们将在下一节中了解有关类对象的更多
信息。 原始的 (在进入类定义之前有效的) 作用域将重新生效，类对象将在这
里与类定义头所给出的类名称进行绑定 (在这个示例中为 "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" 就是有效的属性引用，将分别返回一个整数
和一个函数对象。 类属性也可以被赋值，因此可以通过赋值来更改
"MyClass.i" 的值。 "__doc__" 也是一个有效的属性，将返回所属类的文档字
符串: ""A simple example class""。

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

   x = MyClass()

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

实例化操作 (“调用”类对象) 会创建一个空对象。 许多类都希望创建的对象实
例是根据特定初始状态定制的。 因此一个类可能会定义名为 "__init__()" 的
特殊方法，就像这样:

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

当一个类定义了 "__init__()" 方法时，类的实例化会自动为新创建的类实例唤
起 "__init__()"。 因此在这个例子中，可以通过以下语句获得一个已初始化的
新实例:

   x = MyClass()

当然，"__init__()" 方法还有一些参数用于实现更高的灵活性。 在这种情况下
，提供给类实例化运算符的参数将被传递给 "__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'"。 但是，方法并不是
必须立即调用: "x.f" 是一个方法对象，它可以被保存起来以后再调用。 例如:

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

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

当一个方法被调用时究竟会发生什么？ 你可能已经注意到尽管 "f()" 的函数定
义指定了一个参数，但上面调用 "x.f()" 时却没有带参数。 这个参数发生了什
么事？ 当一个需要参数的函数在不附带任何参数的情况下被调用时 Python 肯
定会引发异常 --- 即使参数实际上没有被使用...

事實上，你可能已經猜到了答案：method 的特殊之處在於，實例物件會作為函
式中的第一個引數被傳遞。在我們的例子中，"x.f()" 這個呼叫等同於
"MyClass.f(x)"。一般來說，呼叫一個有 *n* 個引數的 method，等同於呼叫一
個對應函式，其引數列表 (argument list) 被建立時，會在第一個引數前插入
該 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" 都 "C" 类的指向函数对象的属性，因此它们都是 "C"
实例的方法 --- 其中 "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>

The name "BaseClassName" must be defined in a scope containing the
derived class definition.  In place of a base class name, other
arbitrary expressions are also allowed.  This can be useful, for
example, when the base class is defined in another module:

   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>

对于多数目的来说，在最简单的情况下，你可以认为搜索从父类所继承属性的操
作是深度优先、从左到右的，当层次结构存在重叠时不会在同一个类中搜索两次
。 因此，如果某个属性在 "DerivedClassName" 中找不到，就会在 "Base1" 中
搜索它，然后（递归地）在 "Base1" 的基类中搜索，如果在那里也找不到，就
将在 "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 代码通常可以通过传入一个模拟了该
数据类型的方法的类作为替代。 例如，如果你有一个基于文件对象来格式化某
些数据的函数，你可以定义一个带有 "read()" 和 "readline()" 方法以便从字
典串缓冲区获取数据的类，并将其作为参数传入。

实例方法对象 也具有属性: "m.__self__" 就是带有 "m()" 方法的实例对象，
而 "m.__func__" 就是该方法所对应的 函数对象。


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

了解了迭代器协议背后的机制后，就可以轻松地为你的类添加迭代器行为了。
定义 "__iter__()" 方法用于返回一个带有 "__next__()" 方法的对象。 如果
类已定义了 "__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

可以用生成器来完成的任何功能同样可以通用前一节所描述的基于类的迭代器来
完成。 但生成器的写法更为紧凑，因为它会自动创建 "__iter__()" 和
"__next__()" 方法。

另一個關鍵的特性在於，區域變數和執行狀態會在每次呼叫之間自動被儲存。這
使得該函式比使用 "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) 之類的東西。
