Python | 1 日 120 分 × 7 日アプリ学習:クラスで作る商品管理アプリ(中級編)

Web APP Python
スポンサーリンク

5日目のゴール

5日目のテーマは
「クラスで作った商品オブジェクトを“ファイルに保存・読み込みできるようにすること” です。

ここまでであなたは、Product クラスを作り、
複数の商品オブジェクトをリストで管理し、
CRUD、検索、並び替えまでできるようになりました。

でも、今のままだと
アプリを終了した瞬間、すべてのデータは消えてしまいます。

今日はここに

商品オブジェクトを「保存できる形」に変換する
ファイル(JSON)に書き出す
ファイルから読み込んで、Product オブジェクトを復元する

という「クラス版・永続化(データを残す)」を足していきます。


なぜ「そのまま」ファイルに書けないのか

オブジェクトはメモリの中だけの存在

まず、ここをはっきりさせます。

Product オブジェクトは、Python が動いている間だけ
メモリの中に存在する「実物」です。

例えば、こんなコードがあったとします。

p = Product("p001", "ノートPC", 120000, 5)
Python

この p は「Product 型のオブジェクト」ですが、
これをそのままファイルに write(p) のように書くことはできません。

ファイルに書けるのは、基本的に「文字列」です。
つまり、オブジェクトを一度「文字列にできる形」に変換する必要があります。

ここで出てくるのが
「辞書に変換する」→「JSON にする」
という流れです。


Product を「辞書に変換する」メソッドを作る

to_dict という「変換専用メソッド」

オブジェクトをファイルに保存したいとき、
よく使われるパターンが

「オブジェクト → 辞書」
「辞書 → JSON」

という二段階です。

まずは、Product クラスに
「自分自身を辞書に変換するメソッド」を追加します。

class Product:
    def __init__(self, product_id, name, price, stock):
        self.product_id = product_id
        self.name = name
        self.price = price
        self.stock = stock

    def show_info(self):
        print("=== 商品情報 ===")
        print(f"ID: {self.product_id}")
        print(f"名前: {self.name}")
        print(f"価格: {self.price} 円")
        print(f"在庫数: {self.stock}")

    def to_dict(self):
        return {
            "product_id": self.product_id,
            "name": self.name,
            "price": self.price,
            "stock": self.stock,
        }
Python

ここで深掘りしたいポイントは三つです。

一つ目は、to_dict が「このオブジェクトの状態を、辞書として表現している」こと。
二つ目は、辞書のキーが "product_id", "name", "price", "stock" と、はっきり決まっていること。
三つ目は、「ファイルに保存するのはこの辞書」であって、「オブジェクトそのものではない」こと。

つまり、

「Product は、自分を辞書に変換する方法を知っている」

という状態になります。


辞書から Product を復元する from_dict

「逆変換」をクラス側に持たせる

今度は逆方向です。

ファイルから読み込んだ JSON は、
Python では「辞書」や「辞書のリスト」として扱われます。

そこから Product オブジェクトを作るには、
「辞書 → Product」という変換が必要です。

これも、クラス側に「やり方」を持たせてしまうときれいです。

class Product:
    def __init__(self, product_id, name, price, stock):
        self.product_id = product_id
        self.name = name
        self.price = price
        self.stock = stock

    def to_dict(self):
        return {
            "product_id": self.product_id,
            "name": self.name,
            "price": self.price,
            "stock": self.stock,
        }

    @classmethod
    def from_dict(cls, data):
        return cls(
            data["product_id"],
            data["name"],
            data["price"],
            data["stock"],
        )
Python

ここで重要なポイントを、しっかり押さえます。

@classmethod は「クラスメソッド」を定義するためのもの。
from_dict(cls, data)cls は「このメソッドが属しているクラスそのもの」を指す。
return cls(...) と書くことで、「Product(…) を呼んでいる」のと同じ意味になる。

つまり、

Product.from_dict(some_dict)

と呼ぶと、その辞書から Product オブジェクトが一つ復元されます。

この「to_dict / from_dict のペア」は、
クラスでデータを扱うときの超定番パターンです。


複数の商品を「辞書のリスト」に変換する

products リストを保存用の形にする

アプリ側では、複数の商品を
products というリストで持っていました。

products = [p1, p2, p3, ...]
Python

これをファイルに保存するには、
「Product オブジェクトのリスト」ではなく
「辞書のリスト」に変換する必要があります。

def products_to_list_dict(products):
    return [p.to_dict() for p in products]
Python

この一行の中で、実はかなり大事なことをやっています。

[p.to_dict() for p in products] は「リスト内包表記」と呼ばれる書き方で、
「products の中の各 Product に対して to_dict を呼び、その結果の辞書を並べたリストを作る」
という意味になります。

イメージとしては、こうです。

Product オブジェクトのリスト
to_dict を通す
→ 辞書のリスト

この「変換してから保存する」という発想が、
クラスとファイルをつなぐカギになります。


辞書のリストから Product のリストに戻す

読み込み時の「復元処理」

今度は逆方向です。

ファイルから読み込んだ JSON は、
例えばこんな形の「辞書のリスト」になります。

[
    {"product_id": "p001", "name": "ノートPC", "price": 120000, "stock": 5},
    {"product_id": "p002", "name": "マウス", "price": 1500, "stock": 30}
]
Python

これを Product のリストに戻す関数を作ります。

def list_dict_to_products(data_list):
    products = []
    for data in data_list:
        product = Product.from_dict(data)
        products.append(product)
    return products
Python

ここでのポイントは、

Product.from_dict(data) で「辞書から Product を一つ作っている」こと。
それを products リストに追加して、最後に返していること。

これで、

辞書のリスト
from_dict を通す
→ Product オブジェクトのリスト

という流れができました。


JSON ファイルに保存する

json.dump と「辞書のリスト」

ここから、いよいよファイルです。

Python には JSON を扱うための json モジュールがあります。

import json
Python

商品一覧をファイルに保存する関数を作ります。

def save_products_to_file(products, filename):
    data_list = products_to_list_dict(products)

    try:
        with open(filename, "w", encoding="utf-8") as f:
            json.dump(data_list, f, ensure_ascii=False, indent=2)
        print(f"商品情報をファイルに保存しました: {filename}")
    except OSError as e:
        print("ファイルの保存中にエラーが発生しました。")
        print("詳細:", e)
Python

ここで深掘りしたいポイントは四つです。

一つ目は、products_to_list_dict で「オブジェクトのリスト → 辞書のリスト」に変換していること。
二つ目は、json.dump(data_list, f, ...) で「辞書のリストを JSON としてファイルに書き込んでいる」こと。
三つ目は、ensure_ascii=False によって日本語がそのまま読める形で保存されること。
四つ目は、indent=2 で見やすく整形された JSON になること。

つまり、

「ファイルに書いているのは JSON であり、その中身は“辞書のリスト”」

という構造です。


JSON ファイルから読み込む

json.load と「辞書のリスト → Product のリスト」

次に、保存したファイルから商品一覧を復元する関数を作ります。

def load_products_from_file(filename):
    try:
        with open(filename, "r", encoding="utf-8") as f:
            data_list = json.load(f)
    except FileNotFoundError:
        print(f"{filename} が見つかりません。空の商品一覧から始めます。")
        return []
    except json.JSONDecodeError:
        print("ファイルの内容が壊れているか、JSON ではありません。空の商品一覧から始めます。")
        return []
    except OSError as e:
        print("ファイルの読み込み中にエラーが発生しました。空の商品一覧から始めます。")
        print("詳細:", e)
        return []

    products = list_dict_to_products(data_list)
    print(f"商品情報をファイルから読み込みました: {filename}")
    return products
Python

ここでの重要ポイントは、

json.load(f) で「JSON → 辞書のリスト」に戻していること。
list_dict_to_products で「辞書のリスト → Product のリスト」に変換していること。
ファイルがない・壊れている場合は、安全のために空のリスト [] を返していること。

これで、

ファイル
→ JSON
→ 辞書のリスト
→ Product のリスト

という復元の流れが完成しました。


アプリ起動時に読み込み、終了時に保存する

main の中に「永続化の流れ」を組み込む

ここまで作った関数を、アプリ全体の流れに組み込みます。

まず、ファイル名を決めておきます。

DATA_FILE = "products.json"
Python

そして main をこう書き換えます。

def main():
    products = load_products_from_file(DATA_FILE)

    while True:
        show_menu()
        choice = input("番号を選んでください: ").strip()

        if choice == "1":
            add_product(products)
        elif choice == "2":
            show_all_products(products)
        elif choice == "3":
            update_product(products)
        elif choice == "4":
            delete_product(products)
        elif choice == "5":
            show_product_detail(products)
        elif choice == "6":
            show_low_stock_products(products)
        elif choice == "7":
            show_products_sorted_by_price(products)
        elif choice == "8":
            show_products_sorted_by_name(products)
        elif choice == "9":
            save_products_to_file(products, DATA_FILE)
        elif choice == "0":
            print("アプリを終了します。変更を保存します。")
            save_products_to_file(products, DATA_FILE)
            break
        else:
            print("不正な入力です。0〜9 のどれかを選んでください。")
Python

ここでの流れを、日本語だけで整理してみます。

アプリ起動時
「products.json があれば読み込んで Product のリストに復元し、products に入れる。
なければ空の products = [] から始める。」

アプリ利用中
「products に対して、登録・更新・削除・検索・並び替えなどを行う。」

アプリ終了時
「今の products の状態を、辞書のリスト → JSON として products.json に保存する。」

この構造が見えたら、
「オブジェクトの世界」と「ファイルの世界」が頭の中でつながっている
と言えます。


5日目のまとめ 今日つかんでほしい感覚

今日の本質は、これです。

Product オブジェクトは、そのままではファイルに書けない。
一度「辞書」に変換してから、JSON として保存する。
to_dictfrom_dict は、「オブジェクト ↔ 辞書」の橋渡しをするペア。
複数の商品は「Product のリスト ↔ 辞書のリスト」として変換する。
アプリ起動時に読み込み、終了時に保存することで、「前回の続きから使えるアプリ」になる。

ここまで来たあなたは、
「クラスで作ったオブジェクトを、ファイルに残してまた復元する」
という、かなり実践的なスキルを手に入れています。

6日目・7日目では、
この商品管理アプリ全体をクラスとしてまとめたり、
コードの構造を自分の言葉で説明できるレベルまで整理していきます。

タイトルとURLをコピーしました