Python

オブジェクト指向

Python

クラス・継承・dunder・dataclass

クラスの定義

__init__・インスタンス・クラス変数・プロパティ

classes.py python
class BankAccount:
    # クラス変数(全インスタンスで共有)
    interest_rate: float = 0.02
    _count: int = 0

    def __init__(self, owner: str, balance: float = 0.0) -> None:
        # インスタンス変数
        self.owner   = owner
        self._balance = balance   # _: 慣例的に非公開
        BankAccount._count += 1

    # プロパティ(getterとsetter)
    @property
    def balance(self) -> float:
        return self._balance

    @balance.setter
    def balance(self, value: float) -> None:
        if value < 0:
            raise ValueError('残高は0以上')
        self._balance = value

    # インスタンスメソッド
    def deposit(self, amount: float) -> None:
        self.balance += amount

    # クラスメソッド(cls を第一引数)
    @classmethod
    def get_count(cls) -> int:
        return cls._count

    # 静的メソッド(self も cls も不要)
    @staticmethod
    def validate_amount(amount: float) -> bool:
        return amount > 0

    # 文字列表現
    def __repr__(self) -> str:
        return f'BankAccount({self.owner!r}, {self._balance})'

    def __str__(self) -> str:
        return f'{self.owner}の口座: {self._balance}円'

acc = BankAccount('Alice', 1000)
acc.deposit(500)
print(acc.balance)  # 1500
print(BankAccount.get_count())  # 1

継承と多態性

super()・多重継承・抽象クラス・MRO

inheritance.py python
from abc import ABC, abstractmethod

# 抽象基底クラス
class Animal(ABC):
    def __init__(self, name: str) -> None:
        self.name = name

    @abstractmethod
    def speak(self) -> str: ...  # サブクラスで実装必須

    def describe(self) -> str:
        return f'{self.name}{self.speak()}と言う'

class Dog(Animal):
    def speak(self) -> str: return 'ワン'

class Cat(Animal):
    def speak(self) -> str: return 'ニャン'

# 多重継承
class Flyable:
    def fly(self) -> str: return '飛んでいます'

class FlyingDog(Dog, Flyable):
    def speak(self) -> str:
        return super().speak() + '(飛びながら)'

# MRO(メソッド解決順序)
print(FlyingDog.__mro__)
# (<class 'FlyingDog'>, <class 'Dog'>, <class 'Animal'>, <class 'Flyable'>, ...)

# super() の正しい使い方
class SavingsAccount(BankAccount):
    def __init__(self, owner: str, balance: float, rate: float) -> None:
        super().__init__(owner, balance)  # 親の __init__ を呼ぶ
        self.rate = rate

    def apply_interest(self) -> None:
        self.deposit(self._balance * self.rate)

# isinstance と issubclass
dog = Dog('ポチ')
isinstance(dog, Dog)    # True
isinstance(dog, Animal) # True(継承関係も検査)
issubclass(Dog, Animal) # True

dunder メソッド

__repr__・__eq__・__len__・__iter__・__enter__

dunder.py python
class Vector:
    def __init__(self, x: float, y: float):
        self.x, self.y = x, y

    # 文字列表現
    def __repr__(self) -> str: return f'Vector({self.x}, {self.y})'
    def __str__(self)  -> str: return f'({self.x}, {self.y})'

    # 演算子オーバーロード
    def __add__(self, other: 'Vector') -> 'Vector':
        return Vector(self.x + other.x, self.y + other.y)
    def __mul__(self, scalar: float) -> 'Vector':
        return Vector(self.x * scalar, self.y * scalar)
    def __abs__(self) -> float:
        return (self.x**2 + self.y**2) ** 0.5

    # 比較
    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Vector): return NotImplemented
        return self.x == other.x and self.y == other.y
    def __lt__(self, other: 'Vector') -> bool:
        return abs(self) < abs(other)

    # コンテナ風
    def __len__(self)  -> int:   return 2
    def __iter__(self):          yield self.x; yield self.y
    def __getitem__(self, i):    return (self.x, self.y)[i]
    def __contains__(self, v):   return v in (self.x, self.y)

# コンテキストマネージャー
class DBConnection:
    def __enter__(self):
        self.conn = connect_db()
        return self.conn
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.conn.close()
        return False  # 例外を再送出

with DBConnection() as conn:
    conn.execute('SELECT 1')

dataclass

ボイラープレートを省略したクラス定義

dataclass.py python
from dataclasses import dataclass, field
from typing import ClassVar

@dataclass
class User:
    # フィールド定義(型ヒント必須)
    id:    int
    name:  str
    email: str
    tags:  list[str]  = field(default_factory=list)  # ミュータブルなデフォルト値
    role:  str        = 'user'  # イミュータブルなデフォルト値

    # クラス変数(dataclass のフィールドから除外)
    count: ClassVar[int] = 0

# 自動生成される:
# __init__、__repr__、__eq__

# frozen=True: イミュータブル(__hash__ も生成される)
@dataclass(frozen=True)
class Point:
    x: float
    y: float

    def distance(self) -> float:
        return (self.x**2 + self.y**2) ** 0.5

# order=True: 比較演算子を生成
@dataclass(order=True)
class Priority:
    sort_index: int = field(init=False, repr=False)
    level: int
    name: str

    def __post_init__(self):
        # __init__ 後に実行
        self.sort_index = self.level

# 使用
u = User(id=1, name='Alice', email='a@b.com', tags=['admin'])
p = Point(3.0, 4.0)
print(p.distance())  # 5.0