クラスを積極的に使おう!
クラスを積極的に使う理由:
リストのインデックスを利用すると可読性が下がります。インデックスに意味を持たせることになるからです。かといって辞書にも問題点があります。
辞書だと何故ダメか:
- 特定のキーが存在しているかのチェックが必要
- 辞書を引数にした関数は、他の形式の辞書で使えないため再利用性が低い(再利用性は低いが、どんな辞書でも引数の形式上受け入れてしまう)
クラスにするメリット:
- isinstanceでクラスのインスタンスであるか検証可能
- 型アノテーションをすると、指定したクラスが引数に渡されるか検証可能
- クラス名で検索すれば、そのクラスが使用されている処理を検出可能
‘dataclass’を使う理由:
可読性を高め、’__init__’メソッドの冗長さを削減することです。
‘property’を使う理由:
属性を減らすことです。
‘classmethod’を使う理由:
外部からimportするクラス・関数が一つだけで済むことです。分けた場合には’Sales’クラスとCSVから読込む関数の二つをimportする必要があります。
import csv
from dataclasses import dataclass
from typing import List
@dataclass
class Sale:
    id: int
    item_id: int
    unit_price: int
    amount: int
    def validate(self):
        if self.unit_price <= 0:
            raise ValueError("Invalid sale.price")
        if self.amount <= 0:
            raise ValueError("Invalid sale.amount")
    
    @property
    def total_price(self):
        """ インスタンス変数の変わり代わりに'property'を使う
        """
        return self.amount * self.unit_price
@dataclass
class Sales:
    data: List[Sale]
    @property
    def price(self):
        return sum(sale.total_price for sale in self.data)
    
    @classmethod
    def from_asset(cls, path="./sales.csv"):
        data = []
        with open(path, encoding="utf-8") as f:
            reader = csv.DictReader(f)
            for row in reader:
                try:
                    sale = Sale(**row)
                    sale.validate()
                except Exception:
                    continue
                data.append(sale)
            
        return cls(data=data)
例外をカスタマイズする!
# カスタマイズしたい例外の親クラスを作成
class MailReceivingError(Exception):
    pretext=''
    
    def __init__(self, message, *args):
        if self.pretext:
            message = f"{self.pretext}: {message}"
        
        super().__init__(message, *args)
# 継承した専用の例外クラスを作成
class MailConnectionError(MailReceivingError):
    pretext = '接続エラー'
class MailAuthError(MailReceivingError):
    pretext = '認証エラー'
class MailHeaderError(MailReceivingError):
    pretext = 'メールヘッダーエラー'
# 実際に使用すると…。
def newmail():
    try:
        get_newest_mail()
    except MailReceivingError as e:
        """ カスタマイズすれば継承した例外クラスは一括で補足可能
        """
        pass