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

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

更新於 發佈於 閱讀時間約 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")

程式範例

本文程式範例收錄於:
https://github.com/wjweng/marathon_python/blob/master/Day1_to_25/marathon_python_day19.py

avatar-img
Wei-Jie Weng的沙龍
47會員
36內容數
留言
avatar-img
留言分享你的想法!
Wei-Jie Weng的沙龍 的其他內容
Python提供集合做為其資料結構,它就如同高中數學所學集合的概念,集合的內容一般來說是具有某種特性的事物的整體,例如考試分數及格的群體、數字1到100內的所有奇數、球箱內所有球的顏色等。因此,在一個集合中,每個元素的地位都是相同且無序,並且只能出現一次,集合和集合之間,也可以進行交集、聯集、差集等
Python依據變數有效的範圍可以區分為全域變數(Global variable)或區域變數(Local variable)。在函數內宣告的變數為區域變數,有效範圍只有在函數內,函數外部無法引用這個變數;在函數以外宣告的變數為全域變數,它的有效範圍為整個檔案,並且在函數內部也可以引用這個變數。
這篇文章將利用之前所學過的一些東西,包括if敘述、串列、while迴圈、函數等等的觀念,來實作一個撲克牌的小遊戲-21點。
在前面的文章中我們學習了關於字典的基本用法,今天再討論更多關於字典的其它用法,以及它和串列、元組等的關聯。
在程式設計中,我們會使用到一些固定不會變動的資料內容,例如一年的月份、物體的邊長、過去一周的氣溫等等,使用串列的結構固然也可以用來儲存這些資料,但串列可以被新增或刪除,不能有效保護這類不可變動的資料。因此,Python也提供了另一種形式的資料結構,稱為元組,它的資料結構和串列相同,但資料的內容不可變
當我們查字典時,會先找到想查詢的單字在字典裡的位置,才能在那個位置找到單字的定義;在Python中,也有一個類似的資料結構稱作字典,字典的鍵(key)就對應到我們要查詢的單字,字典的值(value)則是該單字的定義。
Python提供集合做為其資料結構,它就如同高中數學所學集合的概念,集合的內容一般來說是具有某種特性的事物的整體,例如考試分數及格的群體、數字1到100內的所有奇數、球箱內所有球的顏色等。因此,在一個集合中,每個元素的地位都是相同且無序,並且只能出現一次,集合和集合之間,也可以進行交集、聯集、差集等
Python依據變數有效的範圍可以區分為全域變數(Global variable)或區域變數(Local variable)。在函數內宣告的變數為區域變數,有效範圍只有在函數內,函數外部無法引用這個變數;在函數以外宣告的變數為全域變數,它的有效範圍為整個檔案,並且在函數內部也可以引用這個變數。
這篇文章將利用之前所學過的一些東西,包括if敘述、串列、while迴圈、函數等等的觀念,來實作一個撲克牌的小遊戲-21點。
在前面的文章中我們學習了關於字典的基本用法,今天再討論更多關於字典的其它用法,以及它和串列、元組等的關聯。
在程式設計中,我們會使用到一些固定不會變動的資料內容,例如一年的月份、物體的邊長、過去一周的氣溫等等,使用串列的結構固然也可以用來儲存這些資料,但串列可以被新增或刪除,不能有效保護這類不可變動的資料。因此,Python也提供了另一種形式的資料結構,稱為元組,它的資料結構和串列相同,但資料的內容不可變
當我們查字典時,會先找到想查詢的單字在字典裡的位置,才能在那個位置找到單字的定義;在Python中,也有一個類似的資料結構稱作字典,字典的鍵(key)就對應到我們要查詢的單字,字典的值(value)則是該單字的定義。