
大部分在學習程式語言的人很常會看到或是聽到一個很抽象的詞:物件導向
對於初學者來說,又出現了一個很難以理解的名詞,而且在學習上的確也是很常讓人碰壁的一部分。雖然有著不小的學習障礙,但是對於提升程式設計能力有很大的幫助,因此建議大家可以多點耐心去接觸並且練習如何使用它。
🧱 物件導向程式設計(OOP)
為了掌握Python 的物件導向程式設計,需要了解類別與物件的基本概念,熟悉繼承、多型,以及封裝的應用,並理解特殊方法如 __init__、__str__ 等的使用。
為什麼要學物件導向程式設計?
假設你是一名在銀行工作的工程師,負責為新開戶的顧客建立他們的帳號,並設計一系列帳戶功能(如存款、提款、查詢餘額等)。一開始,如果單純依賴傳統的程式設計方法(例如手動寫程式碼處理每位顧客的帳戶),當有 100 位顧客時,你需要重複撰寫 100 次類似的程式碼,導致浪費大量時間,且不易維護。
然而,有沒有更有效率、更易於擴展的方法?
💡 答案是:利用物件導向設計(OOP)!
物件導向程式設計(OOP, Object-Oriented Programming) 是目前主流的程式設計架構之一。OOP 通過將程式碼結構化為抽象的物件,更高效地組織和管理複雜的業務邏輯。這種設計理念提供了類別(Class)與物件(Object)的架構,可以模擬真實世界中的實體與行為(例如人物、動作、資源等),使程式的設計更符合直觀的現實模型。
優點:
- 提升模組化與重用性:類別和物件讓程式開發更具模組化,可以輕鬆地複用代碼,減少重複撰寫的工作
- 促進代碼可維護性:把邏輯封裝成類別和方法後,改動某一部分不會影響其他部分,便於維護
- 資料封裝:通過封裝(如私有屬性)保護數據不被外部直接修改,增強代碼穩定性
- 支援繼承與多型:輕鬆改進現有邏輯(繼承),並根據需求多態化物件的行為
- 真實世界建模:物件導向的架構可以很自然地模擬真實對象和系統行為(如銀行帳戶、圖書館管理等)
缺點:
- 學習曲線較高:對初學者來說,掌握類別、物件、繼承、多型等概念需要一定的學習時間
- 過度設計的風險:小型項目中過度使用 OOP 可能導致複雜而臃腫的架構,降低生產率
- 性能考慮:相較於程序式(動態代碼的不斷執行),OOP 有時因需處理更多結構化代碼而稍顯慢速
📦 類別與物件
定義類別與建立物件
- 類別 (Class) 是物件的藍圖或模板,
- 物件 (Object) 是類別的實例。透過類別定義屬性和方法,並根據類別創建物件。
- 可以快速建立相同類別的物件
- 如:銀行新戶是類別,新戶小明是物件
定義類別
class Person:
"""這是一個描述人的類別"""
pass
# 建立物件
person1 = Person()person2 = Person()print(person1) # <__main__.Person object at ...>
print(person2) # <__main__.Person object at ...>
類別的屬性與方法
- 屬性 (Attributes) 是描述物件的資料或特性,方法 (Methods) 是對象可以執行的動作。
- 物件本身具有的特性以及可以做的事情
- 如:銀行新戶小明具有零存款,預計存入100萬。- 具有零存款:屬性- 存入100萬:方法
__init__
初始化方法
__init__
是類別的 建構函數,在建立物件時自動執行,用於初始化屬性。- 先預設的屬性,之後還可以修改
銀行開戶範例
- 初始化方法屬性: (1) 銀行戶名屬性 (2) 存款餘額
- 存款方法
- 自定義print()資訊
- 為小明創建帳戶
- 小明存入100萬
class BankAccount:
def __init__(self, name, balance=0):
"""
初始化方法,用於建立銀行帳戶
:param name: 新戶名(例如小明)
:param balance: 初始存款額度,預設為 0
"""
self.name = name
self.balance = balance
def deposit(self, amount):
"""
存款方法,將指定金額存入帳戶
:param amount: 存入的金額
"""
if amount > 0: # 檢查存入金額是否有效
self.balance += amount
print(f"{self.name} 存入 {amount} 元。當前餘額為: {self.balance} 元。")
else:
print("存入金額必須大於 0!")
def __str__(self):
"""
自訂字串方法,提供帳戶資訊的友好表示
"""
return f"戶名: {self.name}, 存款餘額: {self.balance} 元"
# 創建新的銀行帳戶物件(小明)
xiaoming = BankAccount(name="小明")
# 初始化訊息
print(xiaoming) # 戶名: 小明, 存款餘額: 0 元
# 小明存入 100 萬
xiaoming.deposit(1000000)
🧬 繼承與多型
父類別與子類別
- 繼承允許我們基於一個父類別 (Parent Class) 建立一個子類別 (Child Class)
- 子類別可以繼承父類別的屬性與方法
- 可以大幅減少重複的程式碼
- 寫過的功能不要再寫一遍
class Animal:
"""父類別:描述動物"""
def __init__(self, name):
self.name = name
def speak(self):
print(f"{self.name} makes a sound.")
class Dog(Animal):
"""子類別:描述狗"""
def speak(self):
print(f"{self.name} barks.")
class Cat(Animal):
"""子類別:描述貓"""
def speak(self):
print(f"{self.name} meows.")
# 建立物件
dog = Dog("Rover")
cat = Cat("Whiskers")
# 呼叫方法
dog.speak() # Rover barks.
cat.speak() # Whiskers meows.
方法覆寫(Override)
- 子類別可以通過覆寫父類別的方法來實現自己的行為。
- 但是可以透過覆寫來修改原本繼承的功能
- 同樣都是speak()方法,但是功能已經不同
class Animal:
"""父類別"""
def speak(self):
print("An animal makes a sound.")
class Dog(Animal):
"""子類別"""
def speak(self):
print("The dog barks.")
# 建立物件
animal = Animal()
dog = Dog()
# 呼叫方法
animal.speak() # An animal makes a sound.
dog.speak() # The dog barks
🔒 封裝、私有屬性與特殊方法
使用 _ 與 __ 定義私有屬性
- 單底線 _:表示「非公開」屬性,但仍可存取(是一種約定)。
- 雙底線 __:觸發名稱重整 (Name Mangling),將屬性名稱改為 _ClassName__attribute,避免外部直接訪問。
- 避免重要屬性被隨意修改
class BankAccount:
def __init__(self, account_owner, balance):
self.account_owner = account_owner
self.__balance = balance # 私有屬性
def deposit(self, amount):
self.__balance += amount
def __str__(self):
return f"{self.account_owner} - Balance: ${self.__balance}"
# 建立物件
account = BankAccount("Alice", 1000)
# 存款
account.deposit(500)
print(account) # Alice - Balance: $1500
# 嘗試直接訪問私有屬性
# print(account.__balance) # AttributeError: 'BankAccount' object has no attribute '__balance'
特殊方法(Advanced)
特殊方法是Python的進階語法,初學者可以不需要會用,但是要看的懂他在幹什麼,因為在欣賞(Trace code)其他人程式時會很常看到相關的用法。
__str__
& __repr__
: 描述
__str__
:用於定義 人類可讀的描述,當 print() 或 str() 被調用時自動執行__repr__
:用於定義 開發者可讀的描述,當在解釋器中回傳對象或執行 repr() 時被呼叫,為開發者提供精確相關的描述
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
return f"Person(name={self.name}, age={self.age})"
def __repr__(self):
return f"<Person: {self.name}, {self.age} years old>"
# 建立物件
person = Person("Alice", 25)
# 呼叫
print(person) # 自動調用 __str__: Person(name=Alice, age=25)
print(repr(person)) # 顯示開發者可讀描述: <Person: Alice, 25 years old>
__del__
:銷毀
- 在 對象被刪除或清理時呼叫,用於釋放資源
- 通常不需要顯式使用,Python 的垃圾回收機制會自動處理
class Resource:
def __init__(self, name):
self.name = name
print(f"Resource {self.name} created.")
def __del__(self):
print(f"Resource {self.name} destroyed.")
# 創建並銷毀對象
res = Resource("File")
del res # Resource File destroyed.
__getattr__
與 __setattr__
:屬性管理
__getattr__
: 當訪問一個未定義的屬性時自動呼叫。__setattr__
: 每次設定屬性時自動呼叫,用於攔截屬性賦值。
class ManagedAttributes:
def __init__(self):
self.attributes = {}
def __getattr__(self, name):
return self.attributes.get(name, f"{name} not found!")
def __setattr__(self, name, value):
if name == "attributes": # 防止遞歸調用
super().__setattr__(name, value)
else:
self.attributes[name] = value
print(f"Set {name} to {value}")
# 使用示例
obj = ManagedAttributes()
obj.foo = 42 # Set foo to 42
print(obj.foo) # 42
print(obj.bar) # bar not found!
__len__
:定義對象長度
- 定義對象的「長度」,用於 len() 函數
- 很常使用的一個方法
class Bookshelf:
def __init__(self, books):
self.books = books
def __len__(self):
return len(self.books)
shelf = Bookshelf(["Book1", "Book2", "Book3"])
print(len(shelf)) # 3
__getitem__
與 __setitem__
:Item操作
__getitem__
:支持通過對象的下標運算(如 obj[key])。__setitem__
:支持對象的下標賦值(如 obj[key] = value)。- 此方法常見於機器學習和深度學習中的Dataloader函數
class Storage:
def __init__(self):
self.data = {}
def __getitem__(self, key):
return self.data.get(key, None)
def __setitem__(self, key, value):
self.data[key] = value
print(f"Set {key} to {value}")
# 使用示例
storage = Storage()
storage["name"] = "Alice" # Set name to Alice
print(storage["name"]) # Alice
__call__
:可調用對象
- 使對象變成「可調用」,像函數一樣使用
- 在許多深度學習的模型建立架構中可見到
class Counter:
def __init__(self):
self.count = 0
def __call__(self):
self.count += 1
print(f"Count: {self.count}")
# 使用示例
counter = Counter()
counter() # Count: 1
counter() # Count: 2
__add__
: 運算符
- 支持重新定義運算符,如 +(加法)、-(減法)、*(乘法)等
- 可用於自定義運算功能
class Number:
def __init__(self, value):
self.value = value
def __add__(self, other):
return Number(self.value + other.value)
def __str__(self):
return str(self.value)
num1 = Number(10)
num2 = Number(20)
result = num1 + num2
print(result) # 30