【Python実践自作問題集】No.4 型に厳密なデータクラス1

# 1. はじめに

この問題集は、Pythonの基礎を習得した後、次の段階へ進みたい人のサポートをすることが目的です。
また、競技プログラミングとは異なり、複雑なアルゴリズムの問題ではなく「**可読性の最大化**」に焦点を当てた問題が中心となります。

[目次](https://qiita.com/__Lily__/items/6f70cd6ae0e80593f09d)
前の問題：[整数？実数？](https://qiita.com/__Lily__/items/cce12790e536667f12bd)
次の問題：[型に厳密なデータクラス2](https://qiita.com/__Lily__/items/87ed19c0087a712692a8)


# 2. 問題

```python:Question
"""
No.4 型に厳密なデータクラス1

次のSampleデータクラスにはvalueという属性があり、float型を指定しています。
しかし、Sampleクラスの利用者から次のような要望が出てきました。
全ての要望及び制約を満たすようにSampleクラスの改修案を検討してください。

なお、データクラスの使用は必須ではありません。
また、value属性への再代入に関しては、この問題では考慮する必要はありません。
初期値の指定に関してだけ考慮すればよいです。

1. Sample(value='abc')はエラーを発生させたい
    指定された値がfloat型へ変換できない場合はエラーを発生させてほしいです。
    データクラスの型には強制力がないため、型チェックで警告は出るものの実行できてしまいます。

2. Sample(value=0)やSample(value='0.0')は許容したい
    指定された値がfloat型へ変換できる場合は許容したいです。
    この場合、Sample(value=0).valueは0ではなく0.0に、
    Sample(value='0.0').valueは'0.0'ではなく0.0にしてください。

制約
    * Sampleクラスはvalueという属性を持ち、初期値を与えることができる。
"""

from dataclasses import dataclass


@dataclass
class Sample:
    value: float
```


# 3. 解答例

```python:データクラスを使用する場合
from dataclasses import dataclass


@dataclass
class Sample:
    value: float

    def __post_init__(self) -> None:
        self.value = float(self.value)
```

```python:pydanticを使用する場合
from pydantic import BaseModel


class Sample(BaseModel):
    value: float
```


# 4. 採点基準

* `float`型へ変換できる場合、`value`属性に変換した値が格納されること
* `float`型へ変換できない場合、エラーが発生すること


 # 5. 解説

## 5.1 データクラスを使用する場合

データクラスでは、`__post_init__`メソッドを使用することで初期化後の処理を記述することができます。

[Python3.7以上のデータ格納はdataclassを活用しよう - Qiita](https://qiita.com/ttyszk/items/01934dc42cbd4f6665d2#__post_init__%E3%81%A7%E5%88%9D%E6%9C%9F%E5%8C%96%E5%BE%8C%E3%81%AE%E5%87%A6%E7%90%86%E3%82%92%E6%9B%B8%E3%81%91%E3%82%8B)

この中で`value`属性を`float`型へ変換し、結果を`self.value`に再代入することで、変換可能な場合は処理が続行され、変換不可能な場合はエラーが発生します。

## 5.2 pydanticを使用する場合

データクラスは便利ですが、属性の型を記述しても強制力が無いのが難点でした。
一方`pydantic`というライブラリでは、データクラスと似た文法を維持しつつ、属性の型に強制力を持たせることが可能になっています。

[pydanticを使って実行時にも型情報が適用されるPythonコードを書く - Qiita](https://qiita.com/koralle/items/93b094ddb6d3af917702)

解答例のように記述するだけで、`value`属性に値が格納される際、自動的に`float`型へ変換されます。
自分で変換処理を書く必要が無いので、シンプルな記述が可能になります。
この他にも便利なバリデーション機能が豊富なので、是非試してみてください。
注意点として、`pydantic v1`と`pydantic v2`で大きく文法が変更されています。
古い情報を見ると、現在では非推奨の書き方が紹介されていることがあるので注意してください。
（見分けるポイントとしては、`@validator`という記述があればv1、`@field_validator`や`@model_validator`という記述があればv2です。）


# 6. テストコード

```python
value = 0.0
sample = Sample(value=value)
assert sample.value == 0.0
assert isinstance(sample.value, float)

value = 0
sample = Sample(value=value)
assert sample.value == 0.0
assert isinstance(sample.value, float)

value = '0.0'
sample = Sample(value=value)
assert sample.value == 0.0
assert isinstance(sample.value, float)

value = 'abc'
try:
    _ = Sample(value=value)
except (Exception,) as e:
    print(type(e))
    print(e)
```

```text:実行結果／データクラスを使用する場合
<class 'ValueError'>
could not convert string to float: 'abc'
```

```text:実行結果／pydanticを使用する場合
<class 'pydantic_core._pydantic_core.ValidationError'>
1 validation error for Sample
value
  Input should be a valid number, unable to parse string as a number [type=float_parsing, input_value='abc', input_type=str]
    For further information visit https://errors.pydantic.dev/2.11/v/float_parsing
```