pythonで作った「買い物リスト作成アプリ」について解説

はじめに

ChatGPTの力もお借りして、コンソール上で動く「買い物リスト作成アプリ」を作成したので、見ていただきたい。

アプリの動作について

ざっくりとこのアプリの動作についてお話しすると、起動して指定した場所にある買い物リストのテキストを読み込む。そして、買い物リストの内容を表示する。その後に、追加、削除、終了の3択をユーザーに選んでもらう。

追加の場合、買い物リストに商品を追加することができる。

削除の場合、買い物リストから商品を削除することができる。

終了の場合、このアプリが終了する。

全体のソースコード

import os
import json


def load_shopping_list(filepath):
    if os.path.exists(filepath):
        with open(filepath, "r", encoding="utf-8") as f:
            items = [line.strip() for line in f.readlines()]
        print("前回の買い物リストを読み込みました。")
    else:
        items = []
    return items


def save_shopping_list(filepath, items):
    with open(filepath, "w", encoding="utf-8") as f:
        for item in items:
            f.write(f"{item}\n")
    print(f"買い物リストを保存しました → {filepath}")


def main():
    # shopping_list.txtを保存したいパスをconfig.jsonに記入して読み込む
    with open("/Users/hiroki/Downloads/Python/shopping_list/config.json", "r", encoding="utf-8") as f:
      config = json.load(f)

    filepath = config["shopping_list_path"]

    items = load_shopping_list(filepath)

    if not items:
        # 初回登録モード
        print("買うものを入力してください(改行で区切る、終了は空行):")
        while True:
            item = input()
            if item == "":
                break
            items.append(item)
        save_shopping_list(filepath, items)
        print("リスト作成が完了しました。また必要になったら起動してください。")
        return

    while True:
        print("\n現在の買い物リスト:")
        for i, item in enumerate(items):
            print(f"{i + 1}. {item}")

        print("\n1. 新しく追加する")
        print("2. 買ったものを削除する")
        print("3. 終了")

        choice = input("選択: ")

        if choice == "1":
            # 商品追加モード
            print("\n追加する商品を改行で入力してください(終了は空行):")
            while True:
                new_item = input()
                if new_item == "":
                    break
                items.append(new_item)

            save_shopping_list(filepath, items)

        elif choice == "2":
            # 買ったもの削除モード
            indexes_to_delete = []
            print("\n買ったものがあれば番号を改行で入力してください(終了は空行):")
            while True:
                number = input()
                if number == "":
                    break
                try:
                    index = int(number) - 1
                    if 0 <= index < len(items):
                        indexes_to_delete.append(index)
                    else:
                        print(f"{number} は無効な番号です")
                except ValueError:
                    print("番号を入力してください")

            # 後ろから削除
            for i in sorted(indexes_to_delete, reverse=True):
                del items[i]

            save_shopping_list(filepath, items)

            if not items:
                print("すべて購入済みになりました。")
                break

        elif choice == "3":
            print("終了します")
            break

        else:
            print("無効な選択です。1〜3を入力してください。")


if __name__ == "__main__":
    main()

リポジトリはこちら。

https://github.com/ki-hi-ro/shopping_list

【動作】起動→買い物リスト

起動すると、買い物リストが表示される。

【動作】3択表示

その後に続くのは、以下の3択。

【動作】1で追加できるようになる→買い物リストを保存

1を選択すると、買い物リストを追加できる。2つの果物を追加した。

一度Enterを押して、もう一度Enterを押すと買い物リストが保存される。そして、果物が追加された買い物リストの内容と、選択肢が表示される。

【動作】2で削除できるようになる→買い物リストを保存

2を押してみよう。そうすると、買ったもの(=買い物リストから削除したいもの)を入力できるようになる。番号で入力する。

Enterを2回押すと、削除された買い物リストが保存され、現在の買い物リストが表示される。選択肢も表示される。

【動作】3でアプリ終了

3を押すとアプリが終了する。

【コード解説】起動→買い物リスト

起動すると、買い物リストが表示される。その理由を見ていこう。

main関数から処理がスタートする。

if __name__ == "__main__":
    main()
def main():

main関数の中では、読み取り専用、エンコーディングUTF-8という指定をしてconfig.jsonを開いて、fというファイルオブジェクトに格納している。withを使用しているので、withブロックを抜けるとファイルが自動で閉じる。

 # shopping_list.txtを保存したいパスをconfig.jsonに記入して読み込む
  with open("/Users/hiroki/Downloads/Python/shopping_list/config.json", "r", encoding="utf-8") as f:
   config = json.load(f)

configには、読み取ったファイルオブジェクトfのjsonをPythonの辞書型で扱えるようにしたものが格納される。

 # shopping_list.txtを保存したいパスをconfig.jsonに記入して読み込む
  with open("/Users/hiroki/Downloads/Python/shopping_list/config.json", "r", encoding="utf-8") as f:
   config = json.load(f)

前提として、冒頭でjsonモジュールを読み込んでいる。

import json

config.jsonは同階層にある。ファイル階層は以下。

config.jsonの中身はこちら。shopping_list.txtが格納してあるicloudのパスが、shopping_list_pathという名前で格納してある。

{
  "shopping_list_path": "/Users/hiroki/Library/Mobile Documents/com~apple~CloudDocs/自作アプリ/shopping_list.txt"
}

main関数に話を戻そう。configオブジェクトからshopping_list_pathの値を取り出して、filepathに格納している。

 filepath = config["shopping_list_path"]

次に、load_shopping_list関数にfilepathを渡した結果をitemsに格納している。

 items = load_shopping_list(filepath)

load_shopping_list関数はこちら。引数としてfilepathを受け取り、if文があり、itemsを返す。

def load_shopping_list(filepath):
    if os.path.exists(filepath):
        with open(filepath, "r", encoding="utf-8") as f:
            items = [line.strip() for line in f.readlines()]
        print("前回の買い物リストを読み込みました。")
    else:
        items = []
    return items

if文では、osモジュールを使用している。これは、冒頭で読み込んでいる。

import os

filepathがある場合は、if文の中身が実行される。ない場合は、elseの中身が実行される。

    if os.path.exists(filepath):
        with open(filepath, "r", encoding="utf-8") as f:
            items = [line.strip() for line in f.readlines()]
        print("前回の買い物リストを読み込みました。")
    else:
        items = []

if文の中では、with文が使用されている。with文の中では、itemsという配列に、読み込んだファイルの行を一行ずつ格納している。strip()で前後の空白を削除している。with文を抜けた後は、「前回の買い物リストを読み込みました。」という文字列を出力する。

        with open(filepath, "r", encoding="utf-8") as f:
            items = [line.strip() for line in f.readlines()]
        print("前回の買い物リストを読み込みました。")

filepathがない場合は、itemsという空の配列が返る。

    else:
        items = []

最終的に、itemsが返る。

    return items

main関数の中で、itemsに値が入ってない場合は、以下が実行される。今回はこちらが実行されるパターンの解説は割愛する。

    if not items:
        # 初回登録モード
        print("買うものを入力してください(改行で区切る、終了は空行):")
        while True:
            item = input()
            if item == "":
                break
            items.append(item)
        save_shopping_list(filepath, items)
        print("リスト作成が完了しました。また必要になったら起動してください。")
        return

if not itemsのブロックを抜けると、whileブロックがある。

    while True:
        print("\n現在の買い物リスト:")
        for i, item in enumerate(items):
            print(f"{i + 1}. {item}")

        print("\n1. 新しく追加する")
        print("2. 買ったものを削除する")
        print("3. 終了")

        choice = input("選択: ")

        if choice == "1":
            # 商品追加モード
            print("\n追加する商品を改行で入力してください(終了は空行):")
            while True:
                new_item = input()
                if new_item == "":
                    break
                items.append(new_item)

            save_shopping_list(filepath, items)

        elif choice == "2":
            # 買ったもの削除モード
            indexes_to_delete = []
            print("\n買ったものがあれば番号を改行で入力してください(終了は空行):")
            while True:
                number = input()
                if number == "":
                    break
                try:
                    index = int(number) - 1
                    if 0 <= index < len(items):
                        indexes_to_delete.append(index)
                    else:
                        print(f"{number} は無効な番号です")
                except ValueError:
                    print("番号を入力してください")

            # 後ろから削除
            for i in sorted(indexes_to_delete, reverse=True):
                del items[i]

            save_shopping_list(filepath, items)

            if not items:
                print("すべて購入済みになりました。")
                break

        elif choice == "3":
            print("終了します")
            break

        else:
            print("無効な選択です。1〜3を入力してください。")

以下の部分で現在の買い物リストが表示される。

        print("\n現在の買い物リスト:")
        for i, item in enumerate(items):
            print(f"{i + 1}. {item}")

【コード解説】3択表示

その後に続くのは、以下の3択。この理由について見ていこう。

以下の部分がそれに該当する。

        print("\n1. 新しく追加する")
        print("2. 買ったものを削除する")
        print("3. 終了")

        choice = input("選択: ")

【コード解説】1で追加できるようになる→買い物リストを保存

1を選択すると、買い物リストを追加できる。2つの果物を追加した。この処理について見ていこう。

1を選択すると、choiceに1が代入される。

        choice = input("選択: ")

choiceが1の場合、以下の条件の中身が実行される。そのため、「追加する商品を改行で入力してください(終了は空行):」という文言が表示される。次に来るのはwhile文。new_itemにユーザーからの入力を代入する。もし、new_itemが空の場合、while文を抜ける。空ではない場合、itemsにnew_itemsが追加される。

        if choice == "1":
            # 商品追加モード
            print("\n追加する商品を改行で入力してください(終了は空行):")
            while True:
                new_item = input()
                if new_item == "":
                    break
                items.append(new_item)

一度Enterを押して、もう一度Enterを押すと買い物リストが保存される。そして、果物が追加された買い物リストの内容と、選択肢が表示される。こちらのソースコードについて解説していく。

空行を入力してwhile文を抜けると、save_shopping_listが待っている。これに、filepathとitemsを渡している。

            save_shopping_list(filepath, items)

save_shopping_list?何だそれは?と思った方に向けて、関数の中身を解説していこう。

こちらがそのsave_shopping_listである。書き込みモードで指定したファイルを開いてfに格納している。買うものが書かれたitemsから一つずつ取り出して、書き込んでいる。

def save_shopping_list(filepath, items):
    with open(filepath, "w", encoding="utf-8") as f:
        for item in items:
            f.write(f"{item}\n")
    print(f"買い物リストを保存しました → {filepath}")

これで、「買い物リストを保存しました → 買い物リストのパス」が表示される理由が納得できたはずだ。

現在の買い物リストが表示される理由に迫っていこう。

これは、while文に以下のように書かれているから。if文を抜けた後は、while文の中にいるので、初めからスタートになる。

    while True:
        print("\n現在の買い物リスト:")
        for i, item in enumerate(items):
            print(f"{i + 1}. {item}")

        print("\n1. 新しく追加する")
        print("2. 買ったものを削除する")
        print("3. 終了")

        choice = input("選択: ")

【コード解説】2で削除できるようになる→買い物リストを保存

2を押してみよう。そうすると、買ったもの(=買い物リストから削除したいもの)を入力できるようになる。番号で入力する。こちらのソースコードについて見ていく。

2を押すと、choiceに2が入る。

        choice = input("選択: ")

choiceに2が入ると、以下の分岐が実行される。

        elif choice == "2":
            # 買ったもの削除モード
            indexes_to_delete = []
            print("\n買ったものがあれば番号を改行で入力してください(終了は空行):")
            while True:
                number = input()
                if number == "":
                    break
                try:
                    index = int(number) - 1
                    if 0 <= index < len(items):
                        indexes_to_delete.append(index)
                    else:
                        print(f"{number} は無効な番号です")
                except ValueError:
                    print("番号を入力してください")

            # 後ろから削除
            for i in sorted(indexes_to_delete, reverse=True):
                del items[i]

            save_shopping_list(filepath, items)

            if not items:
                print("すべて購入済みになりました。")
                break

indexes_to_deleteという空のリストを定義して、「買ったものがあれば番号を改行で入力してください(終了は空行):」という文字列が出力される。

           indexes_to_delete = []
            print("\n買ったものがあれば番号を改行で入力してください(終了は空行):")

その後にwhile文が続く。ユーザーが入力した数値をnumberに代入する。もしnumberが空なら、while文を抜ける。try文では、numberを整数型に変換して1を引いたものをindexに代入する。もしindexが0より大きく、itemsの長さよりも小さかったら、indexes_to_deleteにindexを追加する。そうでなかったら、「{ユーザーが入力した番号}は無効な番号です」が表示される。そもそもint(number) – 1ができなかったら、except句が発動する。「番号を入力してください」と表示される。

            while True:
                number = input()
                if number == "":
                    break
                try:
                    index = int(number) - 1
                    if 0 <= index < len(items):
                        indexes_to_delete.append(index)
                    else:
                        print(f"{number} は無効な番号です")
                except ValueError:
                    print("番号を入力してください")

Enterを2回押すと、削除された買い物リストが保存され、現在の買い物リストが表示される。選択肢も表示される。

これは以下の部分で説明できる。「1. 新しく追加する」の処理と同様に、save_shopping_list関数を使用している。

            save_shopping_list(filepath, items)

そして、「2. 買ったものを削除する」の分岐

        elif choice == "2":

を抜けると、再びwhile文の最初に戻る。

    while True:
        print("\n現在の買い物リスト:")
        for i, item in enumerate(items):
            print(f"{i + 1}. {item}")

        print("\n1. 新しく追加する")
        print("2. 買ったものを削除する")
        print("3. 終了")

        choice = input("選択: ")

ちなみに、以下の部分で、itemsからユーザーが入力した番号の買うものを削除している。del items[i]でitemsのインデックスがiの要素を削除することができる。indexes_to_deleteを大きい順に並べ替えることで、複数のインデックスがある場合に、インデックスがずれてしまうのを防いでいる。

            # 後ろから削除
            for i in sorted(indexes_to_delete, reverse=True):
                del items[i]

例えば、[0, 1, 2]というインデックスのリストがあるとしよう。そして、[‘a’, ‘b’, ‘c’]から順にインデックスを指定して削除すると、まずはインデックス0の’a’が削除される。このとき[‘b’, ‘c’]となる。次にインデックス1を削除したいのだが、もともと意図していた’b’ではなく、’c’が削除されてしまう。

大きい順に並べ替えて[2, 1, 0]とすると、まずインデックス2の’c’が削除される。このとき[‘a’, ‘b’]となる。次に、インデックス1の’b’が削除される。このとき[‘a’]となる。最後にインデックス0の’a’が削除される。

インデックスが大きい順に削除することで、最初に意図していたインデックス番号の要素を削除することができる。

itemsがない場合は、「すべて購入済みになりました。」が表示されて、whileループを抜ける。

            if not items:
                print("すべて購入済みになりました。")
                break

【コード解説】3でアプリ終了

3を押すとアプリが終了する。

この理由。それはこちら。

        elif choice == "3":
            print("終了します")
            break

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

投稿ID : 28717