不間斷 Python 挑戰 Day 19 - 物件導向程式設計:類別

2022/01/06閱讀時間約 10 分鐘
到目前為止,我們所學習的都是程序性的程式設計(procedural programming),也就是程式碼是透過一連串的指令組成的程序或函數,由上而下依序執行不同的程序或是呼叫函數來完成程式的功能。
Python其實是一種物件導向的程式(object oriented programming, 簡稱OOP)語言,Python的所有資料型別皆為物件,也允許開發者創造自己的物件。由於這種物件的概念易於擴充、維護與重複使用,被現代多數的程式語言所採用,也適合用來開發大型的專案。

從程序性的程式設計談起

假設我們要建立一個學生的分數登錄系統,在這個系統裡,我們需要記錄學生的姓名、各個科目的成績,也要能夠修改、查詢成績,以及增加新的科目。依照之前所學習的,可以用一個字串變數來記錄學生的姓名,用一個字典來記錄該學生每一科的分數。
student1_name = "Jack"
student1_score = {"Math": 0, "Physics": 0, "Chemistry": 0}
要修改其中一科的分數,先確認該科目是否存在於字典的鍵中,再修改對應的值。
if "Math" in student1_score:
  student1_score["Math"] = 80
同理,也可以取得其中一科的分數。
if "Math" in student1_score:
  print(student1_score["Math"])
若要新增一個新的科目,可直接增加一組鍵值對。
if "Art" not in student1_score:
  student1_score["Art"] = 0
接著我們要再登錄第二位學生的分數,以上的流程就要再重複一遍,似乎也還好,但如果有100位,甚至1000位學生的成績要登錄,這個過程就很可能變得很冗長且不易維護。
student2_name = "Rose"
student2_score = {"Math": 0, "Physics": 0, "Chemistry": 0}
...
...

物件導向程式設計-類別(Class)

就以上的觀察,我們可以這樣歸類:每位學生都是一個獨立的個體,屬於「學生」這個類別,每位學生都有不同的名字、分數,這些屬於每位學生個別的屬性(attribute),我們可以登錄或查詢每位學生各個科目的分數,或是新增新的科目,這些動作就屬於每位學生可以操作的方法(method);就如同在Python中,字典的資料型態是一個類別,我們所創造的每一個字典型態的資料都是字典這個類別的實體(instance),也稱為物件(object),而在字典一節所提過的keys()、values()、items()等就屬於字典的方法。

定義類別

類別的名稱,根據PEP 8的建議,要將所有單字合併,並且開頭的字母要大寫。定義類別的基本語法如下:
class MyClass:
  ......
以前例來說,我們可以定義一個Student類別,這裡的pass語法表示該區塊內容可以先跳過不執行。
class Student:
  pass

類別的初始化(Initialization)

類別的初始化是當一個宣告為這個類別的物件被建立時所會自動執行的方法,也稱為建構方法(constructor),它有一個固定的名稱為「__init__(self)」,其中的self是必須的,它代表了這個類別創造的物件本身,在初始化時會自動傳入。
下例中建立了一個空的建構方法,並創造一個屬於這個類別的student_1的物件。
class Student:
  # initialiation
  def __init__(self):
    pass
# create an object
student_1 = Student()
類別的建立可以沒有「__init__(self)」這個初始化函數,代表沒有參數需要被初始化,也可以傳入額外的參數,我們在後面會看到。

類別的屬性

類別的屬性區分為類別屬性(class attribute)與實體屬性(instance attribute)。類別屬性定義於建構方法之外,如下例中的classname,它不需要創造該類別的物件即可調用,一旦它被修改,所有的物件都會受到影響。
class Student:
  # class attribute
  classname = "My Class"
  # initialiation
  def __init__(self):
    pass
# access class attribute
print(Student.classname)
# create an object
student_1 = Student()
# modify class attribute
Student.classname = "Jack's Class"
print(Student.classname)
print(student_1.classname)
執行後可看到在student_1物件被創造出來前,即可透過Student.classname取得Student類別的classname屬性,並且當它被修改後,student_1物件取得的classname屬性也跟著改變。
My Class
Jack's Class
Jack's Class
實體屬性建立於建構方法內,或是在創造物件後透過「物件.屬性」生成,若建構方法內的實體屬性需要在物件建立時被初始化,可以在初始化函數__init__(self)後帶上該參數,並在函數內指派給該實體屬性。實體屬性在物件建立之後才可被調用,每個物件的實體屬性互相獨立,修改任一個物件的實體屬性不會影響到其它物件。
如以下範例,當創建Student類別的物件時傳入學生姓名到__init__(self, name)的name中,並指派給self.name,每個物件都有獨立的self.name屬性;此外,也建立了存放學生科目與成績的字典self.score。
class Student:
  # class attribute
  classname = "My Class"
  # initialiation
  def __init__(self, name):
    # instance attribute
    self.name = name
    self.score = {"Math": 0, "Physics": 0, "Chemistry": 0}
# create an object
student_1 = Student("Jack")
student_2 = Student("Rose")
print(student_1.name)
print(student_2.name)
執行結果:
Jack
Rose

類別的方法

類別方法的建立方式和前面提到的函數類似,差異在於類別方法和建構方法同樣必須傳入self參數,代表這個類別創造的物件本身,並且類別方法只有該類別所創造的物件才可調用。
以下範例中延續前面的架構,並新增了三個類別方法,分別操作登錄成績、查詢成績與新增科目。
class Student:
  # class attribute
  classname = "My Class"
  # initialization
  def __init__(self, name):
    # instance attribute
    self.name = name
    self.score = {"Math": 0, "Physics": 0, "Chemistry": 0}
  # methods
  def set_score(self, subject, score):
    if subject in self.score:
      self.score[subject] = score
  def get_score(self, subject):
    if subject in self.score:
    print(self.score[subject])
  def add_subject(self, subject):
    if subject not in self.score:
      self.score[subject] = 0
回到最一開始的例子,當我們需要完成一個學生的建檔、登錄成績、查詢成績與新增科目,只需要以下四行程式碼:
student_1 = Student("Jack")
student_1.set_score("Math", 80)
student_1.get_score("Math")
student_1.add_subject("Art")
增加新的學生時,Student類別可以重複被使用,不需再增加額外的資料結構與方法。
student_2 = Student("Rose")
student_2.set_score("Physics", 90)
student_2.get_score("Physics")
student_2.add_subject("Music")

程式範例

為什麼會看到廣告
Wei-Jie Weng
Wei-Jie Weng
留言0
查看全部
發表第一個留言支持創作者!