Python 類別與物件 (Class & Object)

Python 是一個物件導向程式語言(Object-Oriented Programming, OOP)。類別是物件的藍圖,定義了物件的屬性和行為。

基本概念

  • 類別 (Class):物件的模板或藍圖
  • 物件 (Object):類別的實例 (instance)
  • 屬性 (Attribute):物件的資料
  • 方法 (Method):物件的行為(函數)

定義類別

使用 class 關鍵字定義類別:

class Dog:
    pass  # 空的類別

# 建立物件(實例化)
my_dog = Dog()
print(type(my_dog))  # <class '__main__.Dog'>

__init__ 方法

__init__ 是建構子,在建立物件時自動執行:

class Dog:
    def __init__(self, name, age):
        self.name = name  # 實例屬性
        self.age = age

# 建立物件
my_dog = Dog("Buddy", 3)
print(my_dog.name)  # Buddy
print(my_dog.age)   # 3

self 參數

self 代表物件本身,所有實例方法的第一個參數必須是 self

class Dog:
    def __init__(self, name):
        self.name = name
    
    def bark(self):
        print(f"{self.name} says: Woof!")
    
    def introduce(self):
        print(f"I'm {self.name}")

dog = Dog("Buddy")
dog.bark()       # Buddy says: Woof!
dog.introduce()  # I'm Buddy
呼叫方法時不需要傳入 self,Python 會自動傳入。

實例屬性 vs 類別屬性

class Dog:
    # 類別屬性 - 所有實例共享
    species = "Canis familiaris"
    
    def __init__(self, name, age):
        # 實例屬性 - 每個實例獨立
        self.name = name
        self.age = age

dog1 = Dog("Buddy", 3)
dog2 = Dog("Max", 5)

# 實例屬性
print(dog1.name)  # Buddy
print(dog2.name)  # Max

# 類別屬性
print(dog1.species)  # Canis familiaris
print(dog2.species)  # Canis familiaris
print(Dog.species)   # Canis familiaris

修改類別屬性

class Counter:
    count = 0
    
    def __init__(self):
        Counter.count += 1  # 透過類別名稱修改

c1 = Counter()
c2 = Counter()
c3 = Counter()
print(Counter.count)  # 3

實例方法、類別方法、靜態方法

class MyClass:
    class_attr = "I'm a class attribute"
    
    def __init__(self, value):
        self.instance_attr = value
    
    # 實例方法 - 第一個參數是 self
    def instance_method(self):
        return f"Instance: {self.instance_attr}"
    
    # 類別方法 - 第一個參數是 cls
    @classmethod
    def class_method(cls):
        return f"Class: {cls.class_attr}"
    
    # 靜態方法 - 沒有 self 或 cls
    @staticmethod
    def static_method():
        return "Static method"

obj = MyClass("Hello")

# 實例方法需要透過物件呼叫
print(obj.instance_method())  # Instance: Hello

# 類別方法可以透過類別或物件呼叫
print(MyClass.class_method())  # Class: I'm a class attribute
print(obj.class_method())      # Class: I'm a class attribute

# 靜態方法可以透過類別或物件呼叫
print(MyClass.static_method())  # Static method
print(obj.static_method())      # Static method

類別方法的應用

類別方法常用於替代建構子:

class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day
    
    @classmethod
    def from_string(cls, date_string):
        year, month, day = map(int, date_string.split('-'))
        return cls(year, month, day)
    
    @classmethod
    def today(cls):
        import datetime
        t = datetime.date.today()
        return cls(t.year, t.month, t.day)

date1 = Date(2024, 12, 8)
date2 = Date.from_string("2024-12-08")
date3 = Date.today()

屬性存取控制

Python 沒有真正的 private,但有命名慣例:

class Person:
    def __init__(self, name, age):
        self.name = name       # 公開屬性
        self._email = None     # 約定私有(單底線)
        self.__password = None # 名稱改編(雙底線)

p = Person("Alice", 25)

# 公開屬性
print(p.name)  # Alice

# 單底線 - 約定私有,但仍可存取
p._email = "alice@example.com"
print(p._email)  # alice@example.com

# 雙底線 - 名稱改編,不建議直接存取
# print(p.__password)  # AttributeError
print(p._Person__password)  # 可以這樣存取,但不建議

Property 裝飾器

使用 @property 建立 getter 和 setter:

class Circle:
    def __init__(self, radius):
        self._radius = radius
    
    @property
    def radius(self):
        return self._radius
    
    @radius.setter
    def radius(self, value):
        if value < 0:
            raise ValueError("Radius cannot be negative")
        self._radius = value
    
    @property
    def area(self):
        import math
        return math.pi * self._radius ** 2

circle = Circle(5)
print(circle.radius)  # 5
print(circle.area)    # 78.53...

circle.radius = 10
print(circle.radius)  # 10

# circle.radius = -1  # ValueError
# circle.area = 100   # AttributeError (沒有 setter)

完整範例

class BankAccount:
    # 類別屬性
    bank_name = "Python Bank"
    
    def __init__(self, owner, balance=0):
        self.owner = owner
        self._balance = balance
    
    @property
    def balance(self):
        return self._balance
    
    def deposit(self, amount):
        if amount > 0:
            self._balance += amount
            print(f"Deposited ${amount}. New balance: ${self._balance}")
        else:
            print("Deposit amount must be positive")
    
    def withdraw(self, amount):
        if amount > self._balance:
            print("Insufficient funds")
        elif amount <= 0:
            print("Withdrawal amount must be positive")
        else:
            self._balance -= amount
            print(f"Withdrew ${amount}. New balance: ${self._balance}")
    
    def __str__(self):
        return f"Account({self.owner}, ${self._balance})"

# 使用
account = BankAccount("Alice", 1000)
print(account)           # Account(Alice, $1000)
account.deposit(500)     # Deposited $500. New balance: $1500
account.withdraw(200)    # Withdrew $200. New balance: $1300
print(account.balance)   # 1300