Python程式設計

Lesson 10: 類別與物件

「類別」(class) 與「物件」(object)是「物件導向」程式設計裡最重要的概念

類別

物件

身為資管人,學會運用「類別」與「物件」是精進程式技巧的不二法門

但是我不懂「物件導向」,Python程式也沒寫過幾個,程式又老是出錯...

別擔心!只要掌握類別與物件的基本用法,外加三大法門,你也可成為Python程式達人

建立類別(藍圖)

基本用法Fundamentals

從類別建立物件(按圖施工、完工)

使用物件(使用)

三大法門Design Tools for Class

封裝

繼承

多型

關鍵一:精熟類別(class)與物件(object) 的基本用法 (1)定義類別 (2)建立與使用物件 (3)使用建構元

類別

物件也叫實例

❶ 定義類別

物件BMW

物件Mercedes

❷ 建立

 使用物件

精熟類別物件基本用法之1:建立類別(藍圖)

類別裡需定義物件的「性質」與「操作方式」

class University():
    """ 定義大學類別 """
    title = "真理大學"    # 定義屬性
    motto = "愛與服務"    # 定義屬性
    def vision(self):    # 定義方法
        return self.title +"的教學宗旨是" + self.motto
# 大學類別,無主程式
class Banks():
    """定義銀行類別"""
    title = "淡水商業銀行"    # 定義屬性
    def motto(self):         # 定義方法
        return "服務至上"

# 銀行類別,無主程式

物件的性質-也就是「屬性」,代表該物件擁有的特徵,定義於類別內,方法外

class TShirt():
    """ T恤類別 """
    color = '紅色'    # 顏色屬性
    size = 'L'        # 尺寸
    price = 100       # 價格
    def changeColor(self, 新顏色):    # 方法: 改變顏色
        self.color = 新顏色

練習1-1-1:定義一個商品的類別Product,該商品有名稱,介紹,價格,圖片四個屬性

「方法」是某個物件可操作的功能。定義於類別內。寫法雖與函式相同,但必須稱為「方法」

class TShirt():
    """ T恤類別 """
    color = '紅色'    # 顏色屬性
    size = 'L'        # 尺寸
    price = 100       # 價格
    def changeColor(self, 新顏色):    # 方法: 改變顏色
        self.color = 新顏色

練習1-1-2:延續1-1的類別Product,該商品有名稱,介紹,價格,圖片四個屬性,請為類別Product加入三個「方法」:設定介紹文字、設定圖片、改變價格

 

提示:注意上例第6行的self不可省略

一個好的類別應該訂出適當的「屬性」與該有的操作「方法」

class Book():
    """ 書籍類別 """
    title = 'Python範例大全' #書名
    author = '王小明'        # 作者
    price = 300             # 單價
    存量 = 30                # 庫存數量
    publisher = '真理出版社'  # 出版社
    
    def addBook(self, t, a , p, s, pub):
        """ 新增一本書籍 """
        self.title = t
        self.author = a
        self.price = p
        self.存量 = s
        self.publisher = pub

範例情境:網路書城的書籍管理

  • 屬性:書名、作者、出版社、庫存數量、價格
  • 方法:書籍的新增、刪除、修改
  • 方法:根據價格範圍查詢書籍

此處只有一個方法,刪除? 修改? 查詢?

精熟類別物件基本用法之2:建立與使用物件。建立後,便可存取「屬性」與呼叫「方法」

class University():
    """ 定義大學類別 """
    title = '真理大學'
    def motto():
      return '愛與服務'
    ....
# 主程式開始
au  = University()    # 建立物件au,此物件是University類別
# au.title 可取得字串 '真理大學'
# au.motto()  可執行motto()方法
class Banks():
    """定義銀行類別"""
    ...
    
bank = Banks()  # 建立銀行物件bank
...

物件變數 = 類別名稱()

建立物件

使用物件

物件變數.屬性名稱

物件變數.方法()

class University():
    """ 定義大學類別 """
    title = "真理大學"    # 定義屬性
    motto = "愛與服務"    # 定義屬性
    def vision(self):    # 定義方法
        return self.title +"的教學宗旨是" + self.motto

# 主程式開始
au  = University()    # 建立物件au,此物件是University這種類別
print("大學名稱是:", au.title)
print(au.vision())

「物件」建立後,透過「物件名稱」存取物件的屬性

練習1-2-1:延續類別Product,先建立一個Product類別的物件,緊接著印出該物件的名稱,介紹與價格

class TShirt():
    """ T恤類別 """
    color = '紅色'    # 顏色屬性
    size = 'L'        # 尺寸
    price = 100       # 價格
    def changeColor(self, 新顏色):    # 方法: 改變顏色
        self.color = 新顏色
# 主程式
myT = TShirt()
print('原本的顏色', myT.color)
myT.changeColor('藍色')
print('新的顏色', myT.color)

「物件」建立後,透過「物件名稱」也可呼叫物件裡的方法

練習1-2-2:延續類別Product,先建立一個Product類別的物件,印出價格,緊接著從鍵盤輸入新的價格,修改物件裡的價格後,再印一次價格,看是否已經改變?

精熟類別物件基本用法之3:建構元。建構元是客製化「物件」的特殊函式,可直接設定物件初值

class TShirt():
    """ T恤類別 """
    color = '紅色'    # 顏色屬性
    size = 'L'        # 尺寸
    price = 100       # 價格
    def changeColor(self, 新顏色):    # 方法: 改變顏色
        self.color = 新顏色
        
# 主程式
t1 = TShirt()    # 第一件T恤
t2 = TShirt()    # 第二件T恤
print(t1.color, t2.color)  # 都是紅色
print(t1.size, t2.size)    # 都是L

用TShirt()建立起來每件T恤都是「紅色」L號?

如何才能在建立物件,依需要設定初值? 建構元

__init__() 便是Python類別裡的建構元(1/3)

class TShirt():
    """ T恤類別 """
    price = 100       # 定義屬性price : 固定為100
    def __init__(self, 顏色, 尺寸):        # 左右各2個底線
        """建構元, 呼叫時必須給顏色與尺寸的初值"""
        self.color = 顏色    # 定義屬性color 設定為參數'顏色'
        self.size = 尺寸     # 定義屬性size  設定為參數'尺寸'

    def changeColor(self, 新顏色):    # 方法: 改變顏色
        self.color = 新顏色

# 主程式
t1 = TShirt('紅色', 'L')    # T恤1, 紅色L號
t2 = TShirt('藍色', 'M')    # T恤2, 藍色M號
print(t1.color, t2.color)
print(t1.size, t2.size)

物件變數 = 類別名稱(屬性1初值, 屬性2初值...)

建構元使用方式

class University():
    """ 定義大學類別 """
    def __init__(self, t, m):
        self.title = t    # 定義屬性title
        self.motto = m    # 定義屬性motto
    def vision(self):    # 定義方法
        return self.title +"的教學宗旨是" + self.motto
# 主程式開始
au  = University('真理大學', '愛與服務')    # 建立物件au
print("大學名稱是:", au.title)
print(au.vision())

範例:在建構元中定義title, motto兩屬性, 初值由參數t, m而來

__init__() 便是Python類別裡的建構元(2/3)

練習1-3-1:修改Product類別,加入建構元,並在建構元內設定所有初值

class Banks():
    """定義銀行類別"""
    def __init__(self, name, money):
        self.account = name    # 定義屬性account,初值為name參數值
        self.balance = money   # 定義屬性balance,初值為money
        self.title = '淡水商業銀行'  # 定義屬性title, 初值固定
# 主程式
acc = Banks('王小明', 10000)
print(acc.title, '銀行帳戶明細')
print('帳戶%s的存款有%d' % (acc.account, acc.balance))

範例:在建構元中定義account, balance兩屬性

初值由參數name, money而來

也可同時有其他初值固定的屬性, 如title

__init__() 便是Python類別裡的建構元(3/3)

小結:類別與物件基本用法-類別應訂出適當的「屬性」與該有的操作「方法」

Employee類別

員工編號
員工姓名

getId(): 取得員工編號
getName(): 取得員工姓名

屬性

方法

Account類別

銀行名稱
帳號
帳戶餘額

getBalance(): 存款餘額
saveMoney(m): 存款withdraw(m): 提款

屬性

方法

屬性夠了嗎? 方法夠了嗎? 

視程式需求而定!

class Account():
    """定義帳號類別"""
    def __init__(self, bank, acc, money):
        self.title = bank    # 銀行名稱
        self.account = acc   # 帳號
        self.balance = money # 餘額
    def saveMoney(self, money):
        """存款"""
        self.balance += money        # 更新餘額
        print('存款 %d 完成, 餘額 %d' % (money, self.balance )) 
    def withdraw(self, money):
        """提款"""
        if (money <= self.balance):  
            self.balance -= money    # 提款
            print('提款 %d 完成, 餘額 %d' % (money, self.balance))
        else:                        # 餘額不足
            print('餘額不足, 目前餘額', self.balance)
    def getBalance(self):
        """查詢餘額"""
        return self.balance

acc = Account('第一銀行','Wang', 10000)
print('開戶銀行', acc.title)
print('目前餘額:', acc.getBalance())
acc.saveMoney(2000)   # 存款
acc.withdraw(1000)    # 提款
acc.withdraw(20000)   # 餘額不足

Account類別: 3個屬性, 3個方法

class Employee():
    """員工類別"""
    def __init__(self, id, 姓名):
        self.eid = id              # 屬性eid
        self.name = 姓名            # 屬性name
    
    def getId(self):
        """取得員工編號"""
        return self.eid
    
    def getName(self):
        """取得員工姓名"""
        return self.name

e1 = Employee('E0001', '李小華')
e2 = Employee('E0002', '張小明')
print(e1.getName(), '的員工編號是:', e1.getId())
print(e2.getName(), '的員工編號是:', e2.getId())

Employee類別: 2個屬性, 2個方法

練習1-4-1:試著完成Product類別,加入新增產品,修改產品兩個方法

關鍵二:熟悉讓類別更具擴充彈性的三大法門—封裝、繼承與多型

類別

繼承

透過繼承「重複使用」屬性與方法

封裝

以權限「保護」類別的屬性與方法

多型

方法的「特化」(易於擴充、降低相依性)

關鍵二之一-物件導向三大法門:封裝(encapsulation)

封裝

屬性

方法

類別

封裝:類別(物件)外無法直接使用屬性或方法的「權限保護」機制

私有屬性:類別(物件)外無法直接修改的屬性。外部可修改,則稱為「公有屬性」

class Account():
    """定義帳號類別"""
    def __init__(self, bank, acc, money):
        self.title = bank    # 銀行名稱
        self.account = acc   # 帳號
        self.balance = money # 餘額
    ### ....省略.... ###

acc = Account('第一銀行','Wang', 10000)
# ...略....
# 未呼叫提款功能withdraw(),直接將餘額歸0
acc.balance = 0    # 外部可直接修改「公有屬性」

私有屬性:屬性名稱前加2底線

class Account():
    """定義帳號類別"""
    def __init__(self, bank, acc, money):
        self.__title = bank    # 銀行名稱
        self.__account = acc   # 帳號
        self.__balance = money # 餘額
    ### ....省略.... ###

acc.__balance = 0    # 此行將出現錯誤訊息

3個屬性都是公有屬性

class Account():
    """定義帳號類別"""
    def __init__(self, bank, acc, money):
        self.__title = bank    # 銀行名稱
        self.__account = acc   # 帳號
        self.__balance = money # 餘額
    def saveMoney(self, money):
        self.__balance += money        # 更新餘額
        print('存款 %d 完成, 餘額 %d' % (money, self.__balance )) 
    def withdraw(self, money):
        if (money <= self.__balance):  
            self.__balance -= money    # 提款
            print('提款 %d 完成, 餘額 %d' % (money, self.__balance))
        else:                        # 餘額不足
            print('餘額不足, 目前餘額', self.__balance)
    def getBalance(self):
        return self.__balance
    def getTitle(self):
        return self.__title
        
acc = Account('第一銀行','Wang', 10000)
print('開戶銀行', acc.getTitle())
print('目前餘額:', acc.getBalance())
acc.saveMoney(2000)   # 存款
acc.withdraw(1000)    # 提款
acc.withdraw(20000)   # 餘額不足

Account類別: 3個私有屬性,多了getTitle()方法

class Employee():
    """員工類別"""
    def __init__(self, id, 姓名):
        self.__eid = id              # 屬性eid
        self.__name = 姓名            # 屬性name
    
    def getId(self):
        """取得員工編號"""
        return self.__eid
    
    def getName(self):
        """取得員工姓名"""
        return self.__name

e1 = Employee('E0001', '李小華')
e2 = Employee('E0002', '張小明')
print(e1.getName(), '的員工編號是:', e1.getId())
print(e2.getName(), '的員工編號是:', e2.getId())

Employee類別: 2個私有屬性

練習2-1-1:修改Product類別,將價格與名稱改為私有屬性,留意其他方法是否也需同步修改

私有方法:類別(物件)外無法直接呼叫的方法。外部可呼叫,則稱為「公有方法」

class Account():
    """定義帳號類別"""
    def __init__(self, bank, acc, money):
        self.__title = bank    # 銀行名稱
        self.__account = acc   # 帳號
        self.__balance = money # 餘額
        self.__rate = 30.5
    
    # 公有方法
    def exchange(self, usd):
        ntd = self.__calulate(usd)
        return ntd
    # 私有方法:名稱前加兩底線
    def __calulate(self, money):
        return money * self.__rate

acc = Account('第一銀行','Wang', 10000)
美元 = int(input('輸入要換的金額(美元)'))
台幣=acc.exchange(美元)
print('美元%d 可換台幣%8.2f' % (美元, 台幣))

私有方法:方法名稱前加2底線

私有方法在外部不能直接呼叫

關鍵二之二-物件導向三大法門:繼承(Inheritance)

父類別/基底類別

公有屬性

公有方法

子類別/衍生類別

公有屬性

公有方法

繼承

自訂其他屬性與方法

擴充

子類別建立時,以父類別為參數。故子類別必須寫在父類別之

class Employee():
    """員工類別"""
    def __init__(self, id, 姓名):
        self.eid = id              # 屬性eid
        self.name = 姓名            # 屬性name
    
    def getId(self):
        """取得員工編號"""
        return self.eid
    
    def getName(self):
        """取得員工姓名"""
        return self.name

class ACompany(Employee):
    pass   # 不做任何事情,所有程式繼續

a1 = ACompany('E0001', '李小華')
a2 = ACompany('E0002', '張小明')
print(a1.getName(), '的員工編號是:', a1.getId())
print(a2.getName(), '的員工編號是:', a2.getId())

子類別(以父類別做為參數)

父類別

class Car():
    """ 基底類別 """
    def __init__(self, w=4, d=4, s=5):
        self.wheel = w        # 公有屬性: 輪胎數量
        self.door = d         # 公有屬性: 車門數量
        self.seat = s     # 公有屬性: 座位數量

myCar = Car()        # 預設值 4輪4門5人座
print('幾門:', myCar.door)
urCar = Car(4, 5, 7)    # 4輪 5門 7人座
print('座位數:',urCar.seat)
        

Car類別

wheel
door
seat

無公有方法

Mpv類別

繼承

擴充

wheel
door
seat

無公有方法

air_bag屬性
brand屬性
getDetails()方法

class Car():
    """ 父類別 / 基底類別 """
    def __init__(self, w=4, d=4, s=5):
        self.wheel = w        # 公有屬性: 輪胎數量
        self.door = d         # 公有屬性: 車門數量
        self.seat = s     # 公有屬性: 座位數量

class Mpv(Car):
    """子類別 / 衍生類別"""
    air_bag = 2
    brand = ""
    def __init__(self, bag=2, brand='Toyota'):
        super().__init__()
        self.air_bag = bag
        self.brand = brand
    def getDetails(self):
        print('廠牌:', self.brand)
        print('氣囊:', self.air_bag)
        print('輪子:', self.wheel)
        print('車門:', self.door)
        print('座位:', self.seat)
myCar = Mpv()        # 預設值 4輪4門5人座, 2氣囊, Toyota
myCar.getDetails()

練習2-2-1:建立地點父類別,有「住址」,經度,緯度三個屬性,另建立子類別景點,擴充屬性「名稱」, 「類型」,最後再擴充方法,該方法能印出子類別所有屬性值

關鍵二之三-物件導向三大法門:多型(Polymorphism)

多型 就是 一字多義 的概念

drive() 方法

每一種交通工具drive方式各不相同,但都叫drive():多型

抽象化(Abstraction)是多型的基本要素。

from abc import ABC, abstractmethod

class AbstractClass(ABC):
    @abstractmethod
    def myMethod(self):
        pass

抽象方法:只有宣告、不實作內容

抽象類別是一個空架子
內含一或多個抽象方法

但有提供「抽象基底類別」在abc模組裡,類別名字叫做ABC(Abstract Base Class)

Python也在abc模組裡,提供「抽象方法」修飾指令@abstractmethod

Python沒有Abstract Class語法

from abc import ABC, abstractmethod
 
class AbstractClassExample(ABC):      # 定義抽象類別
    
    @abstractmethod           # 內有一抽象方法
    def do_something(self):       # 抽象方法的實作內容(必要)
        print("Some implementation!")
        
class AnotherSubclass(AbstractClassExample):   # 繼承上述抽象類別
    # 必須「實作」抽象類別所有定義的「抽象方法」
    def do_something(self):    # 抽象方法實作(會覆蓋原先內容)
        super().do_something()     # super() 父類別
        print("The enrichment from AnotherSubclass")
        
x = AnotherSubclass()    # 建構元:建立物件
x.do_something()         # 使用物件