1. はじめに
この問題集は、Pythonの基礎を習得した後、次の段階へ進みたい人のサポートをすることが目的です。
また、競技プログラミングとは異なり、複雑なアルゴリズムの問題ではなく「可読性の最大化」に焦点を当てた問題が中心となります。
目次
前の問題:型に厳密なデータクラス2
次の問題:No.7 不正な値を許すな
2. 問題
"""
No.6 型に厳密なデータクラス3
次のSampleデータクラスにはvalueという属性があり、float型を指定しています。
また、__post_init__を活用することでvalue属性の値はfloat型に制限されています。
しかし、Sampleクラスの利用者から次のような要望が出てきました。
全ての要望及び制約を満たすようにSampleクラスの改修案を検討してください。
なお、データクラスの使用は必須ではありません。
1. 再代入を禁止したい
sample = Sample(value=0.0)
sample.value = 1.0
というように、再代入したときはエラーを発生させてほしいです。
制約
* Sampleクラスはvalueという属性を持ち、初期値を与えることができる。
* value属性の初期値にfloat型へ変換できる値が指定された場合、変換後の値をvalue属性に格納する。
* value属性の初期値にfloat型へ変換できない値が指定された場合、エラーを発生させる。
"""
from dataclasses import dataclass
@dataclass
class Sample:
value: float
def __post_init__(self) -> None:
self.value = float(self.value)
3. 解答例
from pydantic import BaseModel, ConfigDict
class Sample(BaseModel):
model_config = ConfigDict(frozen=True)
value: float
4. 採点基準
- 初期値が
float
型へ変換できる場合、value
属性に変換した値が格納されること - 初期値が
float
型へ変換できない場合、エラーが発生すること - 再代入する場合、エラーが発生すること
5. 解説
前問、前々問の類題です。
今回の問題では、再代入を禁止するという条件が追加されています。
5.1 pydanticを使用する場合
まずはpydantic
を使用した場合ですが、解答の通りmodel_config = ...
という1行を追加するだけでOKです。
このmodel_config
変数をConfigDict
で上書きすることで、型チェックの挙動を変更することができます。
型の指定を厳密にするstrict
など、他にも色々なオプションがあるので是非調べてみてください。
Configuration - docs.pydantic.dev
なお、挙動の変更についてもpydantic v1
とpydantic v2
で文法が変更されています。
次のようなConfig
クラスを定義する方法は現在非推奨となっているので注意してください。
from pydantic import BaseModel
class Sample(BaseModel):
value: float
class Config:
frozen = True
FastAPIがPydantic v2対応したので、V2移行のポイントを紹介する(意外と簡単) - Zenn
5.2 データクラスを使用する場合
さて、pydantic
を使った解答は前節の通りですが、データクラスを使った場合はどうなるのでしょうか?
再代入を禁止するには、@dataclass
を@dataclass(frozen=True)
に変えればいいです。
from dataclasses import dataclass
from typing import Any
@dataclass(frozen=True)
class Sample:
value: float
def __post_init__(self) -> None:
self.value = float(self.value)
しかし、これを実際に使ってみると次のようなエラーが発生します。
sample = Sample(value=0.0)
dataclasses.FrozenInstanceError: cannot assign to field 'value'
よく考えてみると、オブジェクトを作成するときにvalue
属性に値が設定され、__post_init__
の中でvalue
属性に再代入しています。
つまり、値をfloat
型へ変換する際に再代入が生じてしまうのです。
更に、前問で紹介した__setattr__
をオーバーライドする方法についてですが、frozen=True
と__setattr__
のオーバーライドは併用できません。
次のようなコードはエラーになります。
@dataclass(frozen=True)
class Sample:
value: float
def __setattr__(self, key: str, value: Any) -> None:
if key == 'value':
value = float(value)
super().__setattr__(key, value)
TypeError: Cannot overwrite attribute __setattr__ in class Sample
ということで、型の強制と再代入の禁止の両方をデータクラスで実現するのは厳しいです。
(もしかしたら方法があるかもしれませんが……)
以上の議論から、今回の問題ではpydantic
を使用した方法を模範解答とします。