技術筆記 / Python

Python Class 基本用法筆記

class Dog:
    # 類別屬性(所有實例共享)
    species = "Canis familiaris"

    # 建構子(初始化實例)
    def __init__(self, name, age):
        self.name = name      # 實例屬性
        self.age = age

    # 實例方法
    def bark(self):
        return f"{self.name} 汪汪叫!"

    # 使用實例屬性
    def info(self):
        return f"{self.name} 今年 {self.age} 歲"


# 建立實例
d = Dog("小黑", 3)
print(d.bark())        # 小黑 汪汪叫!
print(d.info())        # 小黑 今年 3 歲
print(d.species)       # Canis familiaris

核心概念

self:指向實例本身,方法第一個參數必須是它。

類別屬性 vs 實例屬性

Dog.species          # 透過類別存取
d.species            # 透過實例存取(找不到實例屬性時往上找類別)
d.name               # 實例屬性,各實例獨立

繼承

class Puppy(Dog):
    def __init__(self, name, age, owner):
        super().__init__(name, age)   # 呼叫父類建構子
        self.owner = owner

    def bark(self):                    # 覆寫(override)
        return f"{self.name} 嗚嗚~"

p = Puppy("阿福", 1, "Kirin")
print(p.bark())        # 阿福 嗚嗚~
print(p.info())        # 繼承自 Dog
print(isinstance(p, Dog))   # True

特殊方法(Dunder Methods)

class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y

    def __repr__(self):                # 給開發者看(除錯)
        return f"Point({self.x}, {self.y})"

    def __str__(self):                 # 給使用者看(print)
        return f"({self.x}, {self.y})"

    def __eq__(self, other):           # == 比較
        return self.x == other.x and self.y == other.y

    def __add__(self, other):          # + 運算
        return Point(self.x + other.x, self.y + other.y)


p1 = Point(1, 2)
p2 = Point(3, 4)
print(p1 + p2)         # (4, 6)
print(p1 == Point(1, 2))   # True
print(repr(p1))        # Point(1, 2)

屬性封裝

class Account:
    def __init__(self, balance):
        self._balance = balance        # 慣例:單底線 = 內部使用
        self.__secret = "私有"          # 雙底線 = name mangling

    @property                          # 讓方法像屬性一樣存取
    def balance(self):
        return self._balance

    @balance.setter                    # 設定時驗證
    def balance(self, value):
        if value < 0:
            raise ValueError("餘額不能為負")
        self._balance = value


a = Account(100)
print(a.balance)       # 100(不用括號)
a.balance = 200        # 走 setter

類別方法與靜態方法

class Circle:
    pi = 3.14159

    def __init__(self, r):
        self.r = r

    @classmethod                       # 第一參數是 cls,常用於替代建構子
    def unit(cls):
        return cls(1)

    @staticmethod                      # 不需要 self / cls,純工具函式
    def is_valid(r):
        return r > 0

c = Circle.unit()          # 建立半徑 1 的圓
Circle.is_valid(5)         # True

常用速查

語法 用途
__init__ 建構子
self 實例參照
super().__init__() 呼叫父類
@property getter
@classmethod 類別方法(cls)
@staticmethod 靜態方法
isinstance(x, Cls) 型別檢查
@dataclass 自動產生 __init__/__repr__(見下)

現代寫法:dataclass

from dataclasses import dataclass

@dataclass
class User:
    name: str
    age: int = 0                       # 有預設值

    def greet(self):
        return f"Hi, {self.name}"

u = User("Kirin", 30)
print(u)               # User(name='Kirin', age=30) 自動 __repr__

Python ABC(抽象基底類別)

from abc import ABC, abstractmethod

class Animal(ABC):                      # 繼承 ABC
    @abstractmethod
    def sound(self):                    # 抽象方法:子類「必須」實作
        ...

    def describe(self):                 # 一般方法:可直接繼承
        return f"我發出 {self.sound()} 的聲音"


# 不能實例化抽象類別
# Animal()  → TypeError: Can't instantiate abstract class

class Dog(Animal):
    def sound(self):                    # 必須實作,否則 Dog 也不能實例化
        return "汪汪"

d = Dog()
print(d.sound())        # 汪汪
print(d.describe())     # 我發出 汪汪 的聲音

核心規則:只要類別有任一未實作的 @abstractmethod,就無法被實例化,強制子類補齊介面。

抽象屬性與其他組合

from abc import ABC, abstractmethod

class Shape(ABC):
    @property
    @abstractmethod                     # 抽象屬性(順序:property 在外)
    def area(self):
        ...

    @classmethod
    @abstractmethod                     # 抽象類別方法
    def from_config(cls, cfg):
        ...

    @staticmethod
    @abstractmethod                     # 抽象靜態方法
    def unit():
        ...


class Circle(Shape):
    def __init__(self, r):
        self.r = r

    @property
    def area(self):
        return 3.14159 * self.r ** 2

    @classmethod
    def from_config(cls, cfg):
        return cls(cfg["r"])

    @staticmethod
    def unit():
        return Circle(1)


c = Circle(2)
print(c.area)           # 12.56636

抽象方法可含預設邏輯(子類用 super)

class Base(ABC):
    @abstractmethod
    def load(self):
        # 抽象方法仍可有實作,供子類 super() 呼叫
        return "共用前處理"

class Impl(Base):
    def load(self):
        prefix = super().load()         # 呼叫父類邏輯
        return f"{prefix} + 自訂"

register:虛擬子類(不繼承也算子類)

from abc import ABC

class Serializable(ABC):
    ...

class LegacyJSON:                       # 沒繼承 Serializable
    def to_json(self): ...

Serializable.register(LegacyJSON)       # 註冊為虛擬子類

print(issubclass(LegacyJSON, Serializable))  # True
print(isinstance(LegacyJSON(), Serializable)) # True
# 注意:register 不強制實作方法,只影響 isinstance/issubclass 判斷

subclasshook:鴨子型別檢查

from abc import ABC, abstractmethod

class Drawable(ABC):
    @abstractmethod
    def draw(self): ...

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Drawable:
            # 只要有 draw 方法就算 Drawable(不需顯式繼承)
            if any("draw" in B.__dict__ for B in C.__mro__):
                return True
        return NotImplemented

class Widget:
    def draw(self): ...

print(issubclass(Widget, Drawable))     # True

ABC vs ABCMeta

from abc import ABC, ABCMeta

# 這兩種寫法等價:
class A(ABC): ...
class B(metaclass=ABCMeta): ...
# 若已有自訂 metaclass,需用 ABCMeta 合併;否則直接繼承 ABC 較簡潔

Protocol:結構型別(常用替代方案)

適合純介面約束、不想強制繼承時,比 ABC 更貼近鴨子型別:

from typing import Protocol, runtime_checkable

@runtime_checkable
class Sized(Protocol):
    def __len__(self) -> int: ...       # 只定義形狀,不需實作

class Box:
    def __len__(self): return 5

def show(x: Sized):                     # 靜態檢查 + 提示
    print(len(x))

show(Box())                             # OK,Box 結構相符
print(isinstance(Box(), Sized))         # True(需 runtime_checkable)

ABC vs Protocol 選型

情境 選擇
要強制子類實作、共用基底邏輯 ABC
有明確繼承階層 ABC
只約束「形狀」、不想綁繼承 Protocol
第三方類別無法改繼承 Protocol 或 register
需要 super() 共用實作 ABC

常用速查

語法 用途
class X(ABC) 宣告抽象類別
@abstractmethod 標記必須實作的方法
@property + @abstractmethod 抽象屬性(property 在外層)
X.register(Cls) 註冊虛擬子類
__subclasshook__ 自訂 issubclass 判斷
metaclass=ABCMeta 需合併自訂 metaclass 時
typing.Protocol 結構型別,免繼承約束

在 GitHub 看原文