Python 物件導向

請學員瞭解關於物件導向的概念及特性。

非物件導向

名詞解釋:

  • 變數 (Variable):代表儲存一個或多個的單一資料資料集,能夠被定義 (Define) 與引用 (Reference)。
  • 函式 (Function):封裝的程式碼(在程式中定義),以能在特定需求時輸入資料與輸出結果。

通常物件導向的程式語言會比較高階,也能夠支援非物件導向的操作。不過有些特例如 C# 語言,所有程式碼必須在物件導向概念中實現,非物件導向的實作則是「靜態函式」對應一般函式;「靜態類別」對應模組 (Module),甚至連進入點 (Entry point) 都必須在類別中。

物件導向

名詞解釋:

  • 類別 / 類型 / 型別 (Class):定義資料形式資料操作
  • 物件 (Object):是類別實例 (Instance),可以被變數儲存。

在擁有物件導向的程式語言中,函式也是一種物件,並且有自己的類別,如匿名函式 (Anonymous function, Function literal, Lambda expression)。

程式中若要獲得物件,是由類別產生,或使用字面表示式 (Literal expression)。而某些能夠支援更抽象概念的程式語言中,類別也是一種物件,因此類別也有自己的類別,稱為元類 (Meta Class),如 Python。

類別有以下特性:

  • 封裝性 (Encapsulation):可以攜帶各種資料或資料集,並且由於物件是實例的關係,每個物件是獨立個體,其資料可以有著不同的內容。
  • 繼承 (Inheritance):定義簡單的類別後,較複雜的類別可以從簡單的類別提取其功能
  • 多形 (Polymorphism):不同類別可能會有相同操作。程式碼在使用該物件時,強型別的程式語言(如 C++)必須特別規範,弱型別的程式語言(如 Python)會嘗試尋找並進行該操作。
  • 抽象性 (Abstraction):透過繼承元類概念可以表達更抽象的物件概念。

以下將示範 Python 程式碼中如何用封裝性繼承呈現各種特性。

封裝性

Python 的基本類型定義語法如下:

# Python 中的類型名稱使用 Camal case(單字首大寫)。
# 每個類型與函式定義之間空兩行。
class MyClass:

    """定義 my class。"""

    def __init__(self, score):
        """初始化函式。
        類別中的 function 稱為方法 (Method),
        其中第一項參數 self 代表此物件。
        實際呼叫時會自動填入。
        """
        print("初始化!")
        # 類別中的「變數」稱為成員 (Member)。
        # 類別中的「名稱」稱為屬性 (Attribute)。
        # members 和 methods 都是 attributes。
        # attributes 使用「點」運算子取得。
        self.score = score
        # 私有 attributes 名稱前加上雙底線。
        # 名稱會被編碼為 "_MyClass__score"。
        # 除非用上述名稱呼叫,
        # 只有本類別的 method 可以呼叫。
        self.__score = score + 10

    def method1(self, p0):
        """一個公開的 method,
        任何使用本物件的程式都可以呼叫。
        """
        print("method 1:", self.__score)
        # 呼叫一個私有 method。
        self.__method2(p0)

    def __method2(self, p0):
        """一個私有的 method。"""
        print("method 2:", p0)


# 初始化一個 MyClass 實例。
a = MyClass(50)  # 初始化!
print(a.score)  # 50
a.method1(20)  # method 1: 60
               # method 2: 20
# 強迫呼叫
print(a._MyClass__score)  # 60
# 強迫呼叫
a._MyClass__method2(30)  # method 2: 30

上面的類型中,呈現了封裝性的效果,一個 MyClass 物件中擁有公有與私有的 attributes,當使用 a 名稱傳送此物件時,隨時可以使用公開 attributes。

繼承

繼承可以將較小類型的 attributes 全部拿來用。在 Python 中,有個類型叫做 object,提供一些預設特性,所有類型如果沒指定都會繼承它。

繼承語法如下:

# 引用斜邊公式 hypotenuse。
from math import hypot

class Point:

    """點資料集。"""

    def __init__(self, x, y):
        """初始化時設定兩座標並顯示。"""
        self.x = x
        self.y = y
        self.__show()

    def __show(self):
        """印出 x 和 y。"""
        print(self.x, self.y)

    def distance(self, c):
        """回傳兩點的距離。"""
        return hypot(c.x - self.x, c.y - self.y)

    def move(self, x, y):
        """移動座標。"""
        self.x = x
        self.y = y


class PointView(Point):

    """檢視用的類型,繼承自點。"""

    def __init__(self, x, y):
        # 引用上一個繼承項的 __init__ 函式。
        super(Coordinate, self).__init__(x, y)
        print("Point view")

    def distance_with_origin(self):
        """此點與原點的距離。"""
        return hypot(self.x, self.y)

    def __repr__(self):
        """覆寫預設函式 __repr__。
        此函式可以定義物件轉成字串時的樣式。
        """
        return f"<Point x={self.x} y={self.y}>"


# 建立點資料 p1。
p1 = Point(20, 30)  # 20 30
# 建立點資料 p2。
p2 = PointView(50, 70)  # 50 70
                        # Point view
# 顯示距離。
print(p2.distance(p1))  # 50
# 顯示 p2 與原點的距離。
print(p2.distance_with_origin())  # 86.02325267042627
# 印出 p1 和 p2。
# 印的時候會呼叫預設函式 __repr__。
print(p1)  # <Point object at 一段記憶體位址>
print(p2)  # <Point x=50 y=70>

上面的範例中,PointView 繼承自 Point,因此會擁有所有 Point 的公有與私有 attributes,但是私有 attributes 會被編成 _Point__show 的樣式,因此不能直接使用。

另外,當 PointView 的屬性名稱與 Point 重複時(如 __init__),會直接覆蓋,因此必須使用 super 函式搜尋到 Point 類型,將 PointView 實體帶入 Point.__init__ 執行。

靜態方法

靜態方法 (static method) 只是掛上類別名稱的函式 (static method)。在 Python 中,靜態方法可以有兩種定義法,稱為 static method 與 class method。

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, p):
        """實做相等運算子 ==。"""
        return self.x == p.x and self.y == p.y

    @staticmethod
    def s_method(x, y):
        """Static method."""
        return Point(x + 10, y + 10)

    @classmethod
    def c_method(cls, x, y):
        return cls(x + 10, y + 10)


p1 = Point.s_method(20, 30)
p2 = Point.c_method(20, 30)
print(p1 == p2)  # True

兩者的差異只在於 class method 的會傳入當前類型,所以在繼承上有差別。

延伸閱讀 - 類別與實體的關係

類別也有 attributes,稱為 class attributes。如 Point.__init__Point._Point__show 等。而 p1.x 是因為上面的範例使用 self.x = x 的語法產生,這是後天賦予物件的,又稱 object attributes。物件可以使用 class attributes,但是類別沒有 object attributes。

關於 class attributes,每個 object 的 class member 其實是使用相同名稱,但是 class method 是複製過來的,如下:

class A:
    a = 10
    def method(self, p0, p1):
        pass

a = A()
b = A()
print(a.a is b.a)  # True
print(a.method is b.method)  # False

是因為下列程式是相同結果的:

a.method(p0, p1)
A.method(a, p0, p1)

Python 內建的 type 函式會回傳實體的類型。而事實上 type 是一種類型,而且是所有類型的元類,因此初始化時可以「作出」實體的類型。

Show Comments