前言
Python 的世界中所有東西都是物件(object),物件包含了屬性(Attribute)及方法(Method)。但在之前我們使用不管是 str 字串物件或是 list 容器物件等物件都是 Python 程式語言內建的物件/資料型別。隨著專案複雜度提升,我們需要定義自己的類別(Class)然後類別來產生我們想要操作的物件(可以想成類別 class 是物件 object 的生產製造機器)。接著就讓我們來學習如何製作自定義的類別吧!
Object物件基礎
在日常生活中我們隨處可以見到物件的蹤影,不管是手機、電腦、冷氣等都可以視為物件。事實上,物件就是程式語言中和生活物品對照的一種方式,也是程式碼組織的方式。在程式語言的世界中,物件是由屬性(Attribute)和操作的方法(Method)所組成的。
Python 內建的物件/資料型別,包含整數(int)、字串(str)、串列(list)、字典(dict)等。
languages = ['Java', 'Python']
# <class 'list'> languages 是 list 類別產生的物件屬於 list 類別
print(type(languages))
# list 物件中有 append 方法可以新增元素到列表中
languages.append('C++')
建立類別和建立物件的差異如下
1.物件表示法:
dog1_name = 'buddy'
dog1_age = 3
dog2_name = 'sherry'
dog2_age = 5
2.類別表示法:
# 定義一個類別
class Dog:
# 類別屬性
species = 'Canis familiaris'
# 類別方法
def __init__(self, name, age):
self.name = name # 實例屬性
self.age = age
def bark(self):
print(f"{self.name} says Woof!")
# 建立物件(實例)
my_dog = Dog(name='Buddy', age=3)
# 存取實例屬性
print(my_dog.name) # 輸出: Buddy
# 呼叫實例方法
my_dog.bark() # 輸出: Buddy says Woof!
# 存取類別屬性
print(Dog.species) # 輸出: Canis familiaris
當內建物件/資料型別不敷使用時,需要更進一步設計自已的資料結構,此時就需要自己定義class並規畫該類別的屬性和方法
設計類別(class)
step 1 建立class名稱
使用class
關鍵字即可定義一個類別,Python 類別名稱使用大駝峰命名法,首字英文大寫,若是多個英文單字連接,首字大寫。例如:NewProduct
, Book
。
Python 一般產生類別語法:
class 類別名稱:
class Product:
step 2 定義類別內的物件屬性
將相關的物件屬性值
封裝到我們類別中,類別要產生物件時需要有這些屬性當參數來當作初始值(也可以不定義初始值),而產生的物件則會有這些屬性。
定義類別和物件的初始設定(__init__
方法為固定命名,self
參數是必須的):
self 代表的是產生的物件本身
class 類別名稱:
# __init__ 是初始化的建構函式 Constructor,也就是當從類別產生物件時不用呼叫會自動執行的方法,參數可以傳入所需要的屬性初始值,也可以不定義初始值。self 為物件本身,這邊我們先記著,在後面實際操作時會比較明白相關的意義
def __init__(self, 屬性值):
# 這裡的意思是創建物件時會把傳入初始屬性值給定給所創建的物件屬性
self.屬性 = 屬性值
練習
class Book:
def __init__(self,name,price,category):
self.name = name
self.price = price
self.category = category
step 3 產生物件
語法:在類別名稱後加上一對小括號,並指派給一個變數即可產生一個類別實例(Instance),也就是我們要的物件。
#物件 = 類別名稱(屬性值)
new_product = Product(name = '電熱水器')
實例:
以電商網站工程師為例,若今天需要設計一個類別來產生商品物件,以方便去管理商品。假設商品物件
需要有名稱、價格和方法可以取得折扣價格。若以list或tuple會因大量的產品屬性和方法而難以管理。
class Product:
def __init__(self,name,price=99):
#self.屬性 = 屬性值
self.name = name
#self.屬性 = 屬性值
self,price = price
練習
class Book:
def __init__(self,name,price,category):
self.name = name
self.price = price
self.category = category
book1 = Book(name = '三隻小豬',price = 200,category = '童話')
book2 = Book(name = '三國演義', price=450,category = '歷史')
#以f-string印出第一個物件(book1)的屬性,並以{}包裹
print(f'第一個書籍名稱為: {book1.name}, 售價為: $ {book1.price}, 商品種類: {book1.category}')
#以分隔隔開打印內容
print('**********')
#以f-string印出第二個物件(book2)的屬性,並以{}包裹
print(f'第二個書籍名稱為: {book2.name}, 售價為:{book2.price},商品種類為{book2.category}')
step 4 定義物件方法
定義類別的物件方法即為讓產生的物件可以使用這些function函式。以下定義了 get_product_discount_price
方法,可以讓我們取得物件的折扣價格。
class Product:
def __init__(self,name,price):
self.name = name
self,price = price
#方法即為類別裡面定義的函式 function,第一個參數需要是 self 物件本身
def Product_discount_price(self,discount_rate):
#函式主體
return self.price*discount_rate
使用我們自定義的類別來產生物件:
lass Product:
def __init__(self,name,price):
self.name = name
self.price = price
def Product_discount_price(self,discount_rate):
return self.price*discount_rate
product1 = Product('iphone手機',10000)
discount_for_product1 = product1.Product_discount_price(0.75)
product2 = Product('Asus手機',3000)
discount_for_product2 = product2.Product_discount_price(0.75)
print('**********')
print(product1.name,product1.price,discount)
print(product2.name,product2.price)
print(f'{product1.name}打75折後價格為{discount_for_product1}元')
print(f'{product2.name}打75折後價格為{discount_for_product2}元')
練習
#定義書籍類別 Book
class Book:
#初始化方法 __init__:
初始化方法用來設定物件的屬性。self 代表實例本身,而 name、price、category 是用來初始化書籍物件的參數。
def __init__(self,name,price,category):
self.name = name
self.price = price
self.category = category
#折扣價格方法 get_discount_price:
這個方法計算書籍的折扣價格,接收 discount_rate 參數,並回傳原價乘以折扣率的結果。
def get_discount_price(self,discount_rate):
return self.price*discount_rate
#建立書籍實例 book1 和 book2:
book1 = Book(name = '三隻小豬',price = 200,category = '童話')
book2 = Book(name = '三國演義', price=450,category = '歷史')
book1_discount_price = book1.get_discount_price(0.9)
print(f'書名為{book1.name},原價為{book1.price},折扣後價格為{book1_discount_price}')
練習:
設計一個長方形類別(Rectangle),其屬性需包含有長(height)、寬,(width),方法則有取得長方形面積(get_area)。請使用該類別產生兩個物件(長寬各為:100x20, 400x100)並回傳面積。
class Rectangle:
def __init__(self,height,width):
self.height = height
self.width = width
def get_area(self):
return self.height*self.width
Rectangle1 = Rectangle(100,20)
Rectangle2 = Rectangle(400,100)
area1 = Rectangle1.get_area()
area2 = Rectangle2.get_area()
print(f'矩形1的長為:{Rectangle1.height} 寬為:{Rectangle1.width} 面積為:{area1}')
print(f'矩形2的長為:{Rectangle2.height} 寬為:{Rectangle2.width} 面積為:{area2}')
進階內容: 物件導向程式設計(Object-oriented programming,簡稱 OOP)
OOP的目的是希望讓程式碼的組成可以更有組織性(相關的程式放在一起)且更易於重複使用,OOP主要有三大觀念:封裝(Encapsulation)、繼承(Inheritance)和多型(Polymorphism)。
封裝(Encapsulation)
物件導向程式設計世界中,透過封裝可以讓我們讓外部程式不能直接操作到我們的類別屬性,意思就是說我們可以把細節封裝起來,讓外部不影響到內部(當程式越來越大,我們可能會希望程式不會被其他地方的程式不小心改到屬性內容)。
class Dog:
def __init__(self, name, age):
# 在 Python 類別初始函式中變數名稱開頭為兩個下底線代表私有變數。這邊在類別初始函式中設置一個公有變數 name 和一個私有變數 __age
self.name = name
self.__age = age
# 取得 __age 私有變數的值
def get_age(self):
print('呼叫 getter 方法')
return self.__age
# 設置 __age 私有變數的值
def set_age(self, age):
print('呼叫 setter 方法')
self.__age = age
# 使用 Python 內建 property 函式建立 get_age 和 set_age 分別對應 getter(), setter() 功能
# age 變數可以用來操作私有變數
age = property(get_age, set_age)
# 使用 Dog 類別並給定初始值建立物件
dog_1 = Dog(name='Jack', age=3)
# 使用 getter 存取到 __age 變數
# 3
print('dog_1.age', dog_1.age)
# 使用 setter 修改 __age 變數
dog_1.age = 7
# 7
print('dog_1.age', dog_1.age)
# 可直接存取公有變數 name
print('dog_1.name', dog_1.name)
# 無法直接存取私有變數 __age,出現錯誤訊息 AttributeError: 'Dog' object has no attribute '__age'
print('dog_1.__age', dog_1.__age)
繼承(Inheritance)
類別可以透過繼承的方式,使用父類別的屬性和方法。舉例來說人類是哺乳類也是動物的一種,動物共同的屬性和方法,透過繼承,人類也會天生具備(例如:年齡、性別等屬性,呼吸、吃東西等方法)。
class Animal:
def __init__(self,age,gender):
self.age = age
self.gender = gender
def eat(self):
print("I am eating")
class Human(Animal):
def __init__(self,income,age,gender,religion):
# 初始化父類別,傳入初始化參數
super().__init__(age, gender)
self.income = income
self.religion = religion
def smoke(self):
print("I am smoking")
h1 = Human(40000,40,"male","Christian")
print(h1.age)
繼承
的觀念可以比喻為當今天有一個遊戲開發的專案時,遊戲內的boss怪物具備某種屬性,而其底下的小怪會繼承這樣的樹性或能力。在開發遊戲時,就可以透過繼承的方式重複利用程式碼。
多型(Polymorphism)
Polymorphism 多型意思是相同的符號或是統一介面在不同情境代表不同意思和使用方式,妥善運用可以增加程式設計的使用彈性。
在程式語言和類型論中,多型(polymorphism)指為不同資料類型的實體提供統一的介面,或使用一個單一的符號來表示多個不同的類型。
1.運算子
相同符號在不同情境代表不同意思,以+
為例:
#3
print(1+2)
#12
print('1'+'2')
2.內建函式
統一介面在不同情境代表不同意思,以len
為例
內建函式傳入的參數不同代表結果也不同,字串的長度、串列元素的個數和字典的 key 數量
#字串使用len,得到字串長度 5
print(len('hello'))
#串列使用len,得到元素個數 3
print(len([1,2,3]))
#字典使用len,得到的是key的數量 2
print(len({'name': 'Leo', 'age': 20}))
3.相同類別(class)名稱的方法
統一介面但不同的執行內容
class Book():
def __init__(self, name, price):
self.name = name
self.price = price
def get_sales_price(self):
#當呼叫 get_sales_price 方法時,會先印出 'Book get_sales_price' 這條訊息,然後再回傳計算值。
print('Book get_sales_price')
# 書本打九折
return self.price * 0.9
class Clothing():
def __init__(self, name, price):
self.name = name
self.price = price
def get_sales_price(self):
print('Clothing get_sales_price')
# 服飾打七折
return self.price * 0.7
book_1 = Book(name='老殘遊記', price=200)
clothing_1 = Clothing(name='帽 T', price=300)
for obj in (book_1, clothing_1):
price = obj.get_sales_price()
print(price)
4.繼承相同類別名稱的方法
統一介面下,執行不同的內容又稱為Method Overriding 方法覆寫
:透過繼承覆寫可以讓相似的類別可以根據需求實作不同方法
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
# 定義統一介面
def get_sales_price():
print('Product get_sales_price')
# 繼承 Product
class Book(Product):
# 根據類別需求方法覆寫
def get_sales_price(self):
print('Book get_sales_price')
return self.price * 0.9
# 繼承 Product
class Clothing(Product):
# 根據類別需求方法覆寫
def get_sales_price(self):
print('Clothing get_sales_price')
return self.price * 0.7
book_1 = Book(name='老殘遊記', price=200)
clothing_1 = Clothing(name='帽 T', price=300)
book_price = book_1.get_sales_price()
clothing_price = clothing_1.get_sales_price()
#Book get_sales_price 180
print(book_price)
print(clothing_price)