【Python初心者のための便利なデコレータ解説 #4】@cached_property で“プロパティ”も爆速化！キャッシュで一度だけ計算

## はじめに
このシリーズでは、Python 初心者にもわかりやすく「便利なデコレータ」の使い方を毎回1つずつ紹介していきます。

💡「そもそもデコレータって何？」という方は、まず以下の記事をご覧ください：
> 👉 [【Python初心者のための便利なデコレータ解説 #0】そもそもデコレータってなに？仕組みと使い方をやさしく解説！](https://qiita.com/ys-B/items/d53f2a91c75b8e5981a2)

---

今回は、`@cached_property` というデコレータを紹介します。
> 🗣 読み方は「キャッシュドプロパティ」です。

これは、**クラスのプロパティ（`@property`）を一度だけ計算し、その後は結果をキャッシュして高速に返す**デコレータです。
> 💡 ざっくり言うと「プロパティ版のキャッシュ」。計算コストが高いプロパティに最適！

「毎回プロパティの計算が重くて困る…」  
そんなときに `@cached_property` を使えば、**最初の1回だけ計算し、2回目以降は一瞬で値を返す**ことができます。

特に以下のような場面で役立ちます：

- データベースやAPIへのアクセス結果をプロパティで返したいとき
- 重たい計算結果を保持しておきたいとき

使い方はとてもシンプルで、たった1行追加するだけ！  
ではさっそく、`@cached_property` の魅力を実例とともに見ていきましょう。

## 💡 そもそも「プロパティ」って？

> **プロパティは、「変数みたいに見えるけど、実は裏で関数が動く仕組み」です。**

Pythonのクラスで `obj.value` のように書いたとき、  
ただの変数にも見えますが、実は計算や処理をさせることもできます。

### 例

```python
class User:
    @property
    def name(self):
        print("計算しています…")
        return "山田太郎"


user = User()
print(user.name)  # ← 変数みたいにアクセスできるけど、裏で関数が実行される！
```

## 🔰 基本の使い方
`@cached_property` を使うには、`functools`からインポートし、プロパティの上に`@cached_property`をつけるだけです。

今回は「重たい計算をするプロパティ」を例に、通常の`@property` と `@cached_property` で比較してみます。

`@property`（毎回計算）
```Python
import time


class Data:
    @property
    def heavy_value(self):
        print("計算中...")
        time.sleep(2)  # 擬似的に重たい処理
        return 100


d = Data()
print(d.heavy_value)
print(d.heavy_value)
```
```
計算中...
42
計算中...
42
```
➡️ 毎回「計算中…」と出て、毎回2秒待たされます。


`@cached_property`（最初だけ計算）
```python
from functools import cached_property
import time


class Data:
    @cached_property
    def heavy_value(self):
        print("計算中...")
        time.sleep(2)
        return 42


d = Data()
print(d.heavy_value)
print(d.heavy_value)
```
出力
```
計算中...
42
42
```
➡️ 「計算中…」は最初の1回だけ。2回目以降はキャッシュから即返されます！

## ⚙️ どうやってキャッシュされているの？
`@cached_property` を使うと、最初にプロパティが呼ばれたときだけ計算し、その結果をインスタンスに保存して再利用してくれます。

同じインスタンスであれば、2回目以降は「計算なし」「爆速」で値が返ってきます。

たとえば以下の流れです：
```python
from functools import cached_property
import time


class Data:
    @cached_property
    def heavy_value(self):
        print("計算中...")
        time.sleep(2)
        return 42

        
d = Data()
d.heavy_value  # ← 計算（2秒かかる）
d.heavy_value  # ← キャッシュから即返却（0秒）
```
> 💡 クラスの各インスタンスごとにキャッシュされます。他のインスタンスでは再計算されます。

## 📌 注意：再計算したい場合は？
通常、`@cached_property`は一度計算したら同じ値を返し続けます。
「値を更新したい」「キャッシュを消したい」という場合は、インスタンスの`__dict__`から該当属性を削除すればOKです。

```Python
from functools import cached_property
import time


class Data:
    @cached_property
    def heavy_value(self):
        print("計算中...")
        time.sleep(2)
        return 42

        
d = Data()
print(d.heavy_value)  # 計算
d.__dict__.pop('heavy_value')  # キャッシュ削除
print(d.heavy_value)  # 再計算
```

> 💡 `__dict__`は「そのオブジェクトが今もっている値（属性）をまとめて持っている特別な辞書」です。

### `__dict__`の中身を見てみよう
```python
from functools import cached_property
import time


class Data:
    @cached_property
    def heavy_value(self):
        print("計算中...")
        time.sleep(2)
        return 42


d = Data()
print("d.__dict__", d.__dict__)         # ← まだ何もキャッシュされていない
print("d.heavy_value", d.heavy_value)      # 計算
print("d.__dict__", d.__dict__)         # ← 'heavy_value' が追加されてる
d.__dict__.pop('heavy_value')  # キャッシュ削除
print("d.__dict__", d.__dict__)         # ← また消えている
print("d.heavy_value", d.heavy_value)      # 再計算
```
出力
```
d.__dict__ {}

計算中...
d.heavy_value 42

d.__dict__ {'heavy_value': 42}

d.__dict__ {}

計算中...
d.heavy_value 42
```
最初は空`{}` → `heavy_value`にアクセスすると計算され、`__dict__`に'heavy_value': 42が追加される → `pop`で消すと再び空`{}`に戻り、再度アクセスするとまた計算されます。

## 📝 終わりに
今回は、`@cached_property` というデコレータを使って、プロパティの計算結果をキャッシュして高速化する方法を紹介しました。

たった1行追加するだけで、重たいプロパティが高速化されるのは本当に便利です！
計算コストが高いプロパティでぜひ活用してみてください。

Python 3.7以前の場合は、`@property+functools.lru_cache()`などで似たようなことができますが、3.8以降は標準で`@cached_property`が使えておすすめです！
> 👉 [【Python初心者のための便利なデコレータ解説 #1】@lru_cache で処理を爆速化！キャッシュの力を体感しよう](https://qiita.com/ys-B/items/e24c0eddc2ee3ad8fdb3)