1.デコレータ
関数に「追加の機能」を後付けする仕組みをデコレータという。
コードの上に @xxx と書くことで、その関数・メソッドに追加処理を「挟み込む」ことができる。
以下よく使う標準デコレータ一覧。※他のものも掲載した一覧は別投稿でやる。
| デコレータ | 説明 | 使用例 |
|---|---|---|
| @property | ゲッターを定義 | オブジェクトの属性アクセス |
| @x.setter | セッターを定義 | 属性への代入時処理 |
| @x.deleter | デリータを定義 | del 文で属性削除 |
| @classmethod | cls を受け取るクラスメソッド | 別コンストラクタ、クラス変数操作 |
| @staticmethod | self/cls を受け取らない静的メソッド | ユーティリティ関数をクラス内に置く |
2.dataclass とは?
dataclasses モジュールの @dataclass デコレータを使うと、
- __init__(コンストラクタ)
- __repr__(デバック用。インスタンス内容を表示してくれる。)
- __eq__(== 比較)
などを 自動で作ってくれる クラス用の仕組みです。
from dataclasses import dataclass
@dataclass
class Person:
name: str #dataclassを使った表現。「属性名:データ型」で属性とデータ型を定義。
age: int #「self.age=age」等で作る必要がない。
p = Person("Alice", 20)
print(p) # Person(name='Alice', age=20)
print(p.name) # Alice
クラスに「データ(属性)」がメインのときに特に有効です。
3.カプセル化 + dataclass の基本形
◼ プロパティでバリデーションするパターン
from dataclasses import dataclass, field
@dataclass
class Person:
_age: int = field(repr=False) # repr に表示したくないので repr=False
@property
def age(self) -> int: #「関数()->型名」とすることで返却値の型を表す。※強制力無
return self._age
@age.setter
def age(self, value: int) -> None: #「関数()->None」でなにも返さない。
if value < 0:
raise ValueError("年齢は0以上にしてください")
self._age = value
p = Person(20)
print(p.age) # 20
p.age = 30 # OK
# p.age = -5 # ValueError
- field(repr=False) で print(p) したときに表示させない
- 内部的には _age で持ちつつ、外からは age でアクセス
普通の class とやることは同じですが、データの宣言がスッキリします。
4.クラスメソッド + dataclass
from dataclasses import dataclass
@dataclass
class Product:
price: int
tax_rate: float = 0.1 # クラス共通のデフォルト税率
def total(self) -> int:
"""税込価格を返すインスタンスメソッド"""
return int(self.price * (1 + self.tax_rate))
@classmethod
def with_tax_rate(cls, price: int, tax_rate: float) -> "Product":
#あるクラスのオブジェクトを返すときは「->"クラス名"」で表現する。今回は自クラス。
"""税率を指定して Product を生成するクラスメソッド"""
return cls(price=price, tax_rate=tax_rate)
@staticmethod
def is_valid_price(price: int) -> bool:
"""価格として妥当かをチェックする静的メソッド"""
return price >= 0
使い方:
# 通常の生成
p1 = Product(1000)
print(p1.total()) # 1100
# クラスメソッドで生成
p2 = Product.with_tax_rate(2000, 0.08)
print(p2.total()) # 2160
# 静的メソッド
print(Product.is_valid_price(100)) # True
print(Product.is_valid_price(-10)) # False
ポイント:
- price: int のように 型ヒントを書くと同時にフィールド宣言になる
- クラスメソッド・静的メソッドは 普通の class と同じように書ける
- dataclass は「データ定義を簡潔にする」ものなので、ロジック部分(total, with_tax_rate, is_valid_price)はそのまま使える感じです
5.クラス変数 + クラスメソッド + dataclass
from dataclasses import dataclass, field
@dataclass
class Dog:
name: str
count: int = 0
def __post_init__(self):
# dataclass では __init__ の後に __post_init__ が呼ばれる
Dog.count += 1
@classmethod
def how_many(cls) -> int:
return cls.count
使い方:
d1 = Dog("Pochi")
d2 = Dog("Koro")
print(Dog.how_many()) # 2
- dataclass で自動生成された __init__ の後に
追加処理をしたいときは __post_init__ を使うのが定石です。
6.静的メソッド + dataclass
純粋な「ユーティリティ関数」をクラス内にまとめたい場合:
from dataclasses import dataclass
@dataclass
class MathUtil:
"""値は持たないけど、便利関数をまとめたクラス"""
@staticmethod
def add(a: int, b: int) -> int:
return a + b
@staticmethod
def is_even(x: int) -> bool:
return x % 2 == 0
使い方:
print(MathUtil.add(3, 5)) # 8
print(MathUtil.is_even(4)) # True
dataclass を付ける意味はほとんどないですが、
「同じカテゴリの関数をまとめる箱」として使えます。
7.frozen=True で「完全なカプセル化(実質不変)」に近づける
値を一切変えさせたくないデータには frozen=True を使えます。
from dataclasses import dataclass
@dataclass(frozen=True)
class Config:
host: str
port: int
cfg = Config("localhost", 8080)
# cfg.port = 9000 # エラーになる(変更不可)
- 不変オブジェクト(イミュータブル) として扱える
- 「設定情報」「ID など絶対変わらない値」に向いています
8.まとめ
- @dataclass を使うと、データ中心のクラスが圧倒的に短くキレイに書ける
- カプセル化は field() + @property で実現できる
- クラスメソッド・静的メソッドは 普通の class と同じ書き方
- 追加処理は __post_init__ に書く
- frozen=True で「変更不可」な設定用クラスが作れる
