類別

類別 在 Python

49 個練習

關於 類別

Classes會把資料與行為結合在一起。類別用來建立打包在一起的資料與行為的副本或instances,也就是一般所稱的objects。物件可以代表真實世界的實體(例如車子或貓),也可以代表更抽象的概念(例如整數、載具或哺乳類)。類別是物件導向程式設計(OOP)的核心,這種做法要求程式設計者把問題想成一個或多個彼此互動、保存狀態,並對資料採取行動的物件。

類別

類別是新物件型別的定義,新的物件instances就是從類別建立出來的。它們通常會把資料(也就是fields、attributes、properties、data members或variables)和操作這些資料的程式碼或函式(也就是methods)打包在一起。從這個角度來看,類別就像一份藍圖,或是一組指示,可以依照它打造並使用許多型別相似的物件。一個複雜的程式可以有很多類別,每個類別都能建立出許多不同樣貌的物件。從類別建立並客製化物件的過程稱為instantiation(也就是建立該類別的實例)。

在 Python 中定義類別很直接:

class MyClass:
    # Class body goes here

類別屬性

類別欄位(也就是屬性、資料成員或變數)可以加進類別的主體裡。

class MyClass:
    number = 5
    string = "Hello!"

MyClass的實例(物件)可以建立出來,並綁定到某個名稱:

>>> new_object = MyClass()

# Class is instantiated and resulting object is bound to the "new_object" name (variable).
# Note: the object address 'at 0x15adc55b0' will vary by computer.
>>> new_object
<__main__.MyClass at 0x15adc55b0>

Class attributes在所有由類別建立的物件(或實例)之間共用,可以透過dot notation存取,也就是把.放在物件名稱之後、屬性名稱之前:

>>> new_object = MyClass()

# Accessing the class attribute "number" via dot-notation.
>>> new_object.number
5

# Accessing the class attribute "string" via dot-notation.
>>> new_object.string
'Hello!'

# Instantiating an additional object and binding it to the "second_new_object" name.
>>> second_new_object = MyClass()

>>> second_new_object
<__main__.MyClass at 0x15ad99970>

# Second_new_object shares the same class attributes as new_object.
>>> new_object.number == second_new_object.number
True

類別屬性定義在類別本身的主體裡,位在其他任何方法之前。它們屬於類別所有,因此可以在各個實例之間共用。由於這些屬性由類別的所有實例共用,所以可以直接從類別存取和操作它們的值。修改類別屬性的值,會改變_所有從該類別實例化出來的物件_的值:

>>> obj_one = MyClass()
>>> obj_two = MyClass()

# Accessing a class attribute from an object.
>>> obj_two.number
5

# Accessing the class attribute from the class itself.
>>> MyClass.number
5

# Modifying the value of the "number" class attribute.
>>> MyClass.number = 27

# Modifying the "number" class attribute changes the "number" attribute for all objects.
>>> obj_one.number
27

>>> obj_two.number
27

讓一堆物件隨時保持同步的資料,其實沒什麼特別的用處。幸好,從類別建立出來的物件可以用自己的instance attributes(也就是實例屬性、變數或欄位)來客製化。顧名思義,實例屬性是每個物件各自獨有的,可以獨立修改。

用__init__()客製化物件實例化

特殊的「dunder method」(「double underscore method」的簡稱)__init__()可以用來客製化類別的實例,也能用來為物件初始化實例屬性。因為負責初始化實例屬性,__init__()也被稱為class constructor或initializer。__init__()接受一個必要的參數self,它代表剛建立出來的那個物件。接著就可以把實例屬性的資料,當作__init__()的引數傳入,排在self參數之後。

下面這個MyClass現在有兩個實例屬性,分別叫做location_x和location_y。如你所見,這兩個屬性分別被指定到傳入__init__()的location(一個元組)引數的第一個和第二個索引。當你實例化這個類別、建立出物件時,該類別實例的location_x和location_y屬性就會被初始化:

class MyClass:
    # These are class attributes, variables, or fields.
    number = 5
    string = "Hello!"

    # This is the class "constructor", with a "location" parameter that is a tuple.
    def __init__(self, location):

        # This is an instance or object property, attribute, or variable.
        # Note that we are unpacking the tuple argument into two separate instance variables.
        self.location_x = location[0]
        self.location_y = location[1]

# Create a new object "new_object_one", with object property (1, 2).
>>> new_object_one = MyClass((1, 2))

# Create a second new object "new_object_two" with object property (-8, -9).
>>> new_object_two = MyClass((-8, -9))

# Note that new_object_one.location_x and new_object_two.location_x two different values.
>>> new_object_one.location_x
1

>>> new_object_two.location_x
-8

注意,在上面初始化MyClass時,你只需要傳入一個引數:當類別被呼叫時,Python 會自己處理self的傳遞。

Advanced

建立實例變數的另一種方式,是直接透過物件存取類別變數,再把它改成別的值:

>>> obj_one = MyClass()
>>> obj_two = MyClass()
>>> MyClass.string
'Hello!'

>>> obj_two.string = "Hi!"
>>> obj_two.string
'Hi!'

>>> obj_one.string
'Hello!'

現在來看看類別變數改變時會發生什麼事:

>>> MyClass.string = "World!"
>>> obj_two.string

# obj_two.string has not changed
'Hi!'

>>> obj_one.string

# obj_one.string changed
'World!'

在obj_two的情況下,string屬性現在是實例變數,但在obj_one裡仍然是類別變數。

這件事也可以從類別內部來做:

class Demo:
    new_var = 3

    def add_two(self):
        self.new_var += 2

當<object>.add_two()被呼叫、讀到self.new_var += 2的那一刻,new_var就會從類別變數變成同名的實例變數。

如果類別的所有實例在初始化時,都需要某些屬性從相同的值開始,這個做法就很有用。不過,這麼一來實例變數會遮蔽類別變數,使得在被遮蔽的那個實例上無法取用該類別變數。有鑑於此,改用__init__()方法中的self.<attribute>來設定實例屬性,可能會更安全也更清楚。

方法

method是一種function,會綁定到類別本身(也就是類別方法,之後的練習會深入討論),或是類別的實例(物件)。作用於物件或實例的方法,定義時必須把self當作第一個參數。剩下的參數,可以像定義一般的非綁定函式那樣定義。作用於類別的方法,則需要用@classmethod裝飾器來定義,並且(依照慣例)把cls當作第一個參數。類別方法可以直接在類別上呼叫,不需要先從類別建立物件。

class MyClass:
    number = 5
    string = "Hello!"

    # Class constructor.
    def __init__(self, location):

        # Instance properties
        self.location_x = location[0]
        self.location_y = location[1]

    # Class method. Uses the @classmethod decorator, and cls as the first parameter.
    # Can be called without first making an instance of the class.
    @classmethod
    def change_string(cls, new_string):
        #Class attributes are referred to with cls.
        cls.string = new_string
        return cls.string

    # Instance method.  Note "self" as first parameter.
    def change_location(self, amount):
        self.location_x += amount
        self.location_y += amount
        return self.location_x, self.location_y

和存取屬性一樣,呼叫方法只要把.放在物件名稱之後、方法名稱之前就行了。被呼叫的方法不需要把物件複本當作第一個參數,Python 會自動填入self:

class MyClass:
    number = 5
    string = "Hello!"

    def __init__(self, location):
        self.location_x = location[0]
        self.location_y = location[1]

    def change_location(self, amount):
        self.location_x += amount
        self.location_y += amount
        return self.location_x, self.location_y

# Make a new test_object with location (3,7)
>>> test_object = MyClass((3,7))
>>> (test_object.location_x, test_object.location_y)
(3,7)

# Call change_location to increase location_x and location_y by 7.
>>> test_object.change_location(7)
(10, 14)

類別屬性也可以在實例方法內存取,方式和在類別外部存取一樣:

class MyClass:
    number = 5
    string = "Hello!"

    def __init__(self, location):
        self.location_x = location[0]
        self.location_y = location[1]

    # Alter instance variable location_x and location_y
    def change_location(self, amount):
        self.location_x += amount
        self.location_y += amount
        return self.location_x, self.location_y

    # Alter class variable number for all instances from within an instance.
    def increment_number(self):
        # Increment the 'number' class variable by 1.
        MyClass.number += 1


>>> test_object_one = MyClass((0,0))
>>> test_object_one.number
5

>>> test_object_two = MyClass((13, -3))
>>> test_object_two.increment_number()
>>> test_object_one.number
6

用pass佔位或預留實作

在之前的概念練習與練習題骨架中,你應該看過在函式主體裡用pass關鍵字來代替實際的程式碼。

pass關鍵字是語法上合法的佔位符,可以避免 Python 對空白的函式或類別定義拋出語法錯誤。基本上,這就像是對 Python 翻譯員說:「別擔心!我終究會在這裡放程式碼,只是還沒寫而已。」

class MyClass:
    number = 5
    string = "Hello!"

    def __init__(self, location):
        self.location_x = location[0]
        self.location_y = location[1]

    # Alter instance variable location_x and location_y.
    def change_location(self, amount):
        self.location_x += amount
        self.location_y += amount
        return  self.location_x, self.location_y

    # Alter class variable number for all instances.
    def increment_number(self):
        # Increment the 'number' class variable by 1.
        MyClass.number += 1

    # This will compile and run without error, but has no current functionality.
    def pending_functionality(self):
       # Stubbing or place-holding the body of this method.
       pass
透過 GitHub 編輯 連結會在新視窗或分頁中開啟

學習 類別

練習已鎖定

再解鎖 6 個練習,就能練習 類別