見出し画像

PythonでChatGPTと一緒に住所録を作ろう:データの読み込み

上の記事での画面の作成に引き続き、住所録のデータを読み込む方法を学びます。
JSONというファイルフォーマットで書かれた住所のデータをデータベースの代わりに読み込みます。

この記事を読むことによって、データベースなどからQTableWidgetへデータを読み込む方法が理解できます。
また、汎用的に使われる軽量なデータフォーマットJSONの読み書きが理解できます。

JSONで取り込んだデータを住所録で扱う方法を学ぶことでデータベースのデータを扱う方法の基礎となります。

表記について

ChatGPTの記述部分への切り替わり
"ChatGPT#####"もしくは "G : "

筆者(ごろう)の記述部分への切り替わり
ごろう#####”もしくは"ご : "


1.JSONとは

早速始めましょう。
ChatGPTに、「プログラミンでよく使われるJSONとは何ですか」と送信します。


ChatGPT#####(重要部分のみの抜き出し)
JSON(JavaScript Object Notation)は、データを構造化して保存・送受信するための軽量なデータフォーマットです。
もともとはJavaScriptから派生しましたが、現在ではほとんどのプログラミング言語でサポートされています。
主にAPI通信や設定ファイルとして使われることが多いです。

中略…

JSONの基本構造

JSONは、以下の2つのデータ構造を基本にしています:

  • オブジェクト(key-valueのペア)
    波括弧 {} で囲まれ、キーと値がコロン : で区切られます。

  • 配列(順序付きの値のリスト)
    角括弧 [] で囲まれ、値はカンマ , で区切られます。


ごろう#####
上記のような説明が返ってきました。
だいたいの雰囲気がつかめたでしょうか?
百聞は一見に如かずでサンプルを見れば一発ですね。

[
  {
    "name": "山田太郎",
    "age": 25,
    "email": "yamada@example.com",
    "skills": ["Python", "JavaScript", "HTML"],
    "isEmployed": true,
    "address": {
      "city": "東京",
      "postalCode": "100-0001"
    }
  },
  {
    データ2...
  },
  {
    データ3...
  }
]

ファイルで保存される場合は、上記のような内容でテキストファイルとして保存されます。

オブジェクト

オブジェクトとは、{} で囲まれたデータで
データは、"名前 : 値" と書かれます。

つまりオブジェクトとは、一塊のデータの集まりですってことです。
データの入れ物と考えることもできます。

値には、文字列、数値、真偽値、配列、オブジェクトが割り当てられます。

配列

さらにオブジェクトなどをまとめる入れ物として配列 [] がありますってことですね。

配列には、オブジェクトや基本データ型(数値や文字列)を格納できます。

データベースとして使う

なので、今回の住所録では一人分のデータをオブジェクトとして
人数分のオブジェクトを配列に格納してデータベースとして使います。

2.JSONデータの読み込み

ChatGPTに「PythonでJSONデータをファイルから読み込む方法を教えて」とプロンプトを送信します。


ChatGPT#####(必要な部分のみ)

PythonでJSONデータをファイルから読み込むには、標準ライブラリのjsonモジュールを使用します。以下に手順を説明します。

JSONデータのファイル作成

まず、以下のようなJSONデータを含むファイル(例: data.json)を作成します。

[
  {
    "name": "山田太郎",
    "age": 25,
    "email": "yamada@example.com",
    "skills": ["Python", "JavaScript", "HTML"],
    "isEmployed": true,
    "address": {
      "city": "東京",
      "postalCode": "100-0001"
    }
  },
  {
    "name": "鈴木花子",
    "age": 28,
    "email": "hanako@example.com",
    "skills": ["Ruby", "CSS"],
    "isEmployed": false,
    "address": {
      "city": "大阪",
      "postalCode": "530-0001"
    }
  }
]

PythonでJSONデータを読み込む方法

jsonモジュールを使ってファイルを開き、データを読み込みます。

try:
    with open('data.json', 'r', encoding='utf-8') as file:
        data = json.load(file)
    for person in data:
        print(f"名前: {person['name']}, 年齢: {person['age']}")
except FileNotFoundError:
    print("エラー: data.json ファイルが見つかりません。")
except json.JSONDecodeError:
    print("エラー: JSONデータが正しくありません。")

ごろう#####
上記のコードとサンプルデータが返ってきましたのでテストしてみましょう。

data.jsonと言うファイルを作って、JSONデータのサンプルを張り付けて保存します。

json_test.pyと言うファイルを作ってコードを保存して実行してみましょう。
二つのファイルは、同じフォルダに保存して実行してください。

python json_test.py

結果

名前: 山田太郎, 年齢: 25
名前: 鈴木花子, 年齢: 28

うまく結果は、表示されましたか?

分からないところは、ChatGPTに聞いてみてください。

念のために解説しますと

# JSONファイルを開く
with open('data.json', 'r', encoding='utf-8') as file:
    data = json.load(file)  # ファイルからJSONを読み込む

data = json.load(file) でJSONデータの内容を読み込んで
JSONオブジェクトを作成しています。

with とは、何なのか。
file とは、なにか。
'r' のほかに何が指定できるか。
encoding='utf-8'とはなにか。
try: ~ except: とはなにか。(ここには、ありませんが上記のコードの外側のコード部分です。)

確認すると良いでしょう。

3.データの構造

本来、仕様として記事の最初に説明すべきでした。
データの項目についてここで説明します。

データの項目は

  • 姓フリガナ

  • 名フリガナ

  • 郵便番号

  • 住所

  • 電話番号

  • 備考

とします。

データベースとして考えるとこれにIDがあったほうが良いのですが
今回は、練習用のアプリケーションという事で使わないことにします。

これをJSONで書くと

[
    {
        "姓": "鈴木",
        "姓フリガナ": "スズキ",
        "名": "稔",
        "名フリガナ": "ミノル",
        "郵便番号": "844-9047",
        "住所": "香川県君津市台東24丁目6番16号",
        "電話番号": "45-7065-8608",
        "備考": "感謝芸術家族平和友情"
    },
    {
        "姓": "藤田",
        "姓フリガナ": "フジタ",
        "名": "淳",
        "名フリガナ": "アツシ",
        "郵便番号": "160-8650",
        "住所": "神奈川県足立区大中32丁目14番6号 所野コーポ882",
        "電話番号": "090-8162-3050",
        "備考": "夢平和幸福平和安心"
    },
    
    中略...
]

となります。

テスト用のデータ
100件分のデータを作りましたのでテスト用に使ってください。
Faker(と言うライブラリを使って)で自動生成したものです。

4.住所録に組み込む

住所録コード(前回の記事で使用した住所録コード)

import sys
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton,
    QTableWidget, QTableWidgetItem, QHeaderView, QWidget, QLabel, QDialog, QFormLayout
)

class EditDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("編集画面")
        self.setGeometry(200, 200, 400, 300)

        layout = QFormLayout()

        self.last_name_input = QLineEdit()
        self.first_name_input = QLineEdit()
        self.last_name_kana_input = QLineEdit()
        self.first_name_kana_input = QLineEdit()
        self.postal_code_input = QLineEdit()
        self.address_input = QLineEdit()
        self.phone_number_input = QLineEdit()
        self.notes_input = QLineEdit()

        layout.addRow("姓", self.last_name_input)
        layout.addRow("名", self.first_name_input)
        layout.addRow("姓フリガナ", self.last_name_kana_input)
        layout.addRow("名フリガナ", self.first_name_kana_input)
        layout.addRow("郵便番号", self.postal_code_input)
        layout.addRow("住所", self.address_input)
        layout.addRow("電話番号", self.phone_number_input)
        layout.addRow("備考", self.notes_input)

        self.save_button = QPushButton("保存")
        self.cancel_button = QPushButton("キャンセル")

        button_layout = QHBoxLayout()
        button_layout.addWidget(self.save_button)
        button_layout.addWidget(self.cancel_button)
        layout.addRow(button_layout)

        self.setLayout(layout)

class AddressBookApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("住所録")
        self.setGeometry(100, 100, 800, 600)

        # メインウィジェットとレイアウト
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
        main_layout = QVBoxLayout()
        main_widget.setLayout(main_layout)

        # フィルター入力と実行ボタン
        filter_layout = QHBoxLayout()
        self.filter_input = QLineEdit()
        self.filter_input.setPlaceholderText("フィルターを入力")
        self.filter_button = QPushButton("実行")
        filter_layout.addWidget(self.filter_input)
        filter_layout.addWidget(self.filter_button)
        main_layout.addLayout(filter_layout)

        # 一覧表
        self.table = QTableWidget()
        self.table.setColumnCount(8)
        self.table.setHorizontalHeaderLabels([
            "姓", "姓フリガナ", "名", "名フリガナ", "郵便番号", "住所", "電話番号", "備考"
        ])
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
        main_layout.addWidget(self.table)

        # ボタン群
        button_layout = QHBoxLayout()
        self.new_button = QPushButton("新規作成")
        self.edit_button = QPushButton("編集")
        self.delete_button = QPushButton("削除")
        self.save_button = QPushButton("保存")
        self.load_button = QPushButton("読み込み")
        
        button_layout.addWidget(self.new_button)
        button_layout.addWidget(self.edit_button)
        button_layout.addWidget(self.delete_button)
        button_layout.addWidget(self.save_button)
        button_layout.addWidget(self.load_button)
        main_layout.addLayout(button_layout)

        # ボタンの動作
        self.new_button.clicked.connect(self.open_edit_dialog)
        self.edit_button.clicked.connect(self.open_edit_dialog)

    def open_edit_dialog(self):
        dialog = EditDialog(self)
        dialog.exec()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = AddressBookApp()
    window.show()
    sys.exit(app.exec())

クラスAddressBookAppのコンストラクタで
JSONデータを読み込むのが適切ですね。

ちょっと疑問が湧いたのでChatGPTにプロンプトを送ります。
「(ソースを与えて)これは住所録のコードです。
住所録のデータとしてJSONデータを読み込むのは、
GUIを組み立てた前と後どちらが妥当ですか?」


ChatGPT#####

JSONデータを読み込む処理を「GUIを組み立てた後」に実行するのが適切です。以下に理由を説明します:


理由 1: GUIが組み上がっていないとデータを反映できない

  • JSONデータを読み込むと、そのデータをテーブルなどのGUI要素に反映する必要があります。

  • GUIがまだ組み立てられていない段階でデータを読み込もうとすると、テーブルなどのUI要素が存在しないため、エラーが発生します。


理由 2: 分かりやすいコード構造

  • GUIの構築をまず行い、その後にデータの読み込みや初期化処理を行うという順序は、アプリケーションの初期化処理を整理しやすくします。

  • 「GUI構築」と「データのロード」を別々に記述することで、後から修正・拡張する際の可読性も向上します。


ごろう#####
大分省略してますが上記の様に返ってきました。

それでは、JSONの読み込み部分を load_data_from_json()という関数にして
コンストラクタの最下行に追加します。

おまけでコンストラクタが長くなってきたので(見通しが悪い)ので
setup_ui()という、UI組み立て用の専用関数に分けて作ってわかりやすくします。

尚、データはテスト用の"address_book.json"を使っています。

import json # ほかのimport文とまとめる

class AddressBookApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("住所録")
        self.setGeometry(100, 100, 800, 600)

        # GUIを構築
        self.setup_ui()

        # データを読み込む
        self.load_data_from_json("address_book.json") # データ読み込み関数

    def setup_ui(self):
        """GUIを構築するメソッド"""
        # メインウィジェットとレイアウト
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
        main_layout = QVBoxLayout()
        main_widget.setLayout(main_layout)

        # フィルター入力と実行ボタン
        filter_layout = QHBoxLayout()
        self.filter_input = QLineEdit()
        self.filter_input.setPlaceholderText("フィルターを入力")
        self.filter_button = QPushButton("実行")
        filter_layout.addWidget(self.filter_input)
        filter_layout.addWidget(self.filter_button)
        main_layout.addLayout(filter_layout)

        # 一覧表
        self.table = QTableWidget()
        self.table.setColumnCount(8)
        self.table.setHorizontalHeaderLabels([
            "姓", "姓フリガナ", "名", "名フリガナ", "郵便番号", "住所", "電話番号", "備考"
        ])
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
        main_layout.addWidget(self.table)

        # ボタン群
        button_layout = QHBoxLayout()
        self.new_button = QPushButton("新規作成")
        self.edit_button = QPushButton("編集")
        self.delete_button = QPushButton("削除")
        self.save_button = QPushButton("保存")
        self.load_button = QPushButton("読み込み")
        
        button_layout.addWidget(self.new_button)
        button_layout.addWidget(self.edit_button)
        button_layout.addWidget(self.delete_button)
        button_layout.addWidget(self.save_button)
        button_layout.addWidget(self.load_button)
        main_layout.addLayout(button_layout)

        # ボタンの動作
        self.new_button.clicked.connect(self.open_edit_dialog)
        self.edit_button.clicked.connect(self.open_edit_dialog)
        
    def load_data_from_json(self, file_path): # テスト読み込み関数
        """JSONデータを読み込んでテーブルに反映するメソッド"""
        # テスト読み込み
        try:
            with open(file_path, 'r', encoding='utf-8') as file:
                data = json.load(file)
                print(data) # デバッグprint
        except FileNotFoundError:
            print("エラー: data.json ファイルが見つかりません。")
        except json.JSONDecodeError:
            print("エラー: JSONデータが正しくありません。")

    def open_edit_dialog(self):
        dialog = EditDialog(self)
        dialog.exec()

クラスAddressBookApp()を先ほどのテストコードに従って上記の様に書き換えました。

この段階では、load_data_from_json()はテスト用にしか作っていません。
本来は、QTableWidgetへのデータの渡し方を考えて作るのですがまず
ここで確実にデータを読み取れているかをprint()で確かめます。

書き換えたコードを実行してみます。
コンソールにJSONデータがそのまま表示できています。

これでJSONデータを住所録に持ち込むことが出来ました。

5.QTableWidgetに読み込む

まず、ChatGPTにQTableWidgetの使い方を教えてもらいましょう。
「QTableWidgetの使い方を教えて」などとするとサンプルコードとともに基本的な使い方を解説してくれます。

ヘッダーはすでに追加されていますね。
行は、追加することが出来るのでデータを追加するときに行を増やせば良さそうです。
table.setItem(y, x, QTableWidgetItem(データ))とすればデータを追加出来ることが分かりました。

新しいチャットを立ち上げて、address_book.pyの内容を一部コピーしてChatGPTのプロンプトに張り付け以下のプロンプトを送ります。

新しいチャットを立ち上げるのは、返答が今までの内容に合わせてしまい
純粋な質問内容とは違う答えが返ってくるからです。

[
    {
        "姓": "鈴木",
        "姓フリガナ": "スズキ",
        "名": "稔",
        "名フリガナ": "ミノル",
        "郵便番号": "844-9047",
        "住所": "香川県君津市台東24丁目6番16号",
        "電話番号": "45-7065-8608",
        "備考": "感謝芸術家族平和友情"
    },
    {
        "姓": "藤田",
        "姓フリガナ": "フジタ",
        "名": "淳",
        "名フリガナ": "アツシ",
        "郵便番号": "160-8650",
        "住所": "神奈川県足立区大中32丁目14番6号 所野コーポ882",
        "電話番号": "090-8162-3050",
        "備考": "夢平和幸福平和安心"
    },
]

address_book.json ファイルの内容の一部です。

        self.table = QTableWidget()
        self.table.setColumnCount(8)
        self.table.setHorizontalHeaderLabels([
            "姓", "姓フリガナ", "名", "名フリガナ", "郵便番号", "住所", "電話番号", "備考"
        ])
これを上のコードようなQTableWidgetに取り込むにはどんなコードを書けばいいですか。

そうすると次のような回答が返ってきました。


ChatGPT#####

    def load_data_from_json(self, file_path):
        # JSONデータの読み込み
        with open(file_path, "r", encoding="utf-8") as file:
            address_book = json.load(file)

        # データをテーブルに追加
        self.table.setRowCount(len(address_book))  # 行数を設定
        for row, entry in enumerate(address_book):
            self.table.setItem(row, 0, QTableWidgetItem(entry.get("姓", "")))
            self.table.setItem(row, 1, QTableWidgetItem(entry.get("姓フリガナ", "")))
            self.table.setItem(row, 2, QTableWidgetItem(entry.get("名", "")))
            self.table.setItem(row, 3, QTableWidgetItem(entry.get("名フリガナ", "")))
            self.table.setItem(row, 4, QTableWidgetItem(entry.get("郵便番号", "")))
            self.table.setItem(row, 5, QTableWidgetItem(entry.get("住所", "")))
            self.table.setItem(row, 6, QTableWidgetItem(entry.get("電話番号", "")))
            self.table.setItem(row, 7, QTableWidgetItem(entry.get("備考", "")))

ご#####
うまく行きそうですが後ろのfor文以降が見た目が同じことの繰り返しです。
省略できそうですね。

ファイルが無い場合などのERRORハンドリングも書いておきます。

    def load_data_from_json(self, file_path):
        try:
            # JSONデータの読み込み
            with open(file_path, "r", encoding="utf-8") as file:
                address_book = json.load(file)

            # データをテーブルに追加
            self.table.setRowCount(len(address_book))  # 行数を設定
            for row, entry in enumerate(address_book):
                for col, item in enumerate(["姓", "姓フリガナ", "名", "名フリガナ",
                                            "郵便番号","住所", "電話番号", "備考"]):
                    cell = QTableWidgetItem(entry.get(item, ""))
                    self.table.setItem(row, col, cell)
        except FileNotFoundError:
            print("ファイルが見つかりません")
        except json.JSONDecodeError:
            print("JSONデータが壊れています")

self.table.setRowCount()QTableWidgetの行数を設定します。
行数はlen(address_book)、つまりデータの長さを設定します。

enumerateは、for文と一緒に使います。 リストなどがあったときにループの回数に合わせてタプルを作ります。
['a', 'b', 'c'] こんなリストがあったとき、

for t enumerate(['a', 'b', 'c']):
    print(t) 

# 結果
# (0, 'a')
# (1, 'b')
# (2, 'c')

このようにタプルを返します。
タプルとは、(x, y) のような2要素の組のデータ構造です。

それでは、先ほどaddress_book.jsonから取得したデータを
仮にprint()で出力していた部分を
load_data_from_json()で置き換えましょう。

これでデータをQTableWidgetに読み込めるようになりました。

6.全コード

import sys
import json
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton,
    QTableWidget, QTableWidgetItem, QHeaderView, QWidget, QLabel, QDialog, QFormLayout
)

class EditDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("編集画面")
        self.setGeometry(200, 200, 400, 300)

        layout = QFormLayout()

        self.last_name_input = QLineEdit()
        self.first_name_input = QLineEdit()
        self.last_name_kana_input = QLineEdit()
        self.first_name_kana_input = QLineEdit()
        self.postal_code_input = QLineEdit()
        self.address_input = QLineEdit()
        self.phone_number_input = QLineEdit()
        self.notes_input = QLineEdit()

        layout.addRow("姓", self.last_name_input)
        layout.addRow("名", self.first_name_input)
        layout.addRow("姓フリガナ", self.last_name_kana_input)
        layout.addRow("名フリガナ", self.first_name_kana_input)
        layout.addRow("郵便番号", self.postal_code_input)
        layout.addRow("住所", self.address_input)
        layout.addRow("電話番号", self.phone_number_input)
        layout.addRow("備考", self.notes_input)

        self.save_button = QPushButton("保存")
        self.cancel_button = QPushButton("キャンセル")

        button_layout = QHBoxLayout()
        button_layout.addWidget(self.save_button)
        button_layout.addWidget(self.cancel_button)
        layout.addRow(button_layout)

        self.setLayout(layout)

class AddressBookApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("住所録")
        self.setGeometry(100, 100, 800, 600)

        # GUIを構築
        self.setup_ui()

        # データを読み込む
        self.load_data_from_json("address_book.json")

    def setup_ui(self):
        """GUIを構築するメソッド"""
        # メインウィジェットとレイアウト
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
        main_layout = QVBoxLayout()
        main_widget.setLayout(main_layout)

        # フィルター入力と実行ボタン
        filter_layout = QHBoxLayout()
        self.filter_input = QLineEdit()
        self.filter_input.setPlaceholderText("フィルターを入力")
        self.filter_button = QPushButton("実行")
        filter_layout.addWidget(self.filter_input)
        filter_layout.addWidget(self.filter_button)
        main_layout.addLayout(filter_layout)

        # 一覧表
        self.table = QTableWidget()
        self.table.setColumnCount(8)
        self.table.setHorizontalHeaderLabels([
            "姓", "姓フリガナ", "名", "名フリガナ", "郵便番号", "住所", "電話番号", "備考"
        ])
        self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
        main_layout.addWidget(self.table)

        # ボタン群
        button_layout = QHBoxLayout()
        self.new_button = QPushButton("新規作成")
        self.edit_button = QPushButton("編集")
        self.delete_button = QPushButton("削除")
        self.save_button = QPushButton("保存")
        self.load_button = QPushButton("読み込み")
        
        button_layout.addWidget(self.new_button)
        button_layout.addWidget(self.edit_button)
        button_layout.addWidget(self.delete_button)
        button_layout.addWidget(self.save_button)
        button_layout.addWidget(self.load_button)
        main_layout.addLayout(button_layout)

        # ボタンの動作
        self.new_button.clicked.connect(self.open_edit_dialog)
        self.edit_button.clicked.connect(self.open_edit_dialog)

    def load_data_from_json(self, file_path):
        try:
            # JSONデータの読み込み
            with open(file_path, "r", encoding="utf-8") as file:
                address_book = json.load(file)

            # データをテーブルに追加
            self.table.setRowCount(len(address_book))  # 行数を設定
            for row, entry in enumerate(address_book):
                for col, item in enumerate(["姓", "姓フリガナ", "名", "名フリガナ",
                                            "郵便番号","住所", "電話番号", "備考"]):
                    cell = QTableWidgetItem(entry.get(item, ""))
                    self.table.setItem(row, col, cell)
        except FileNotFoundError:
            print("ファイルが見つかりません")
        except json.JSONDecodeError:
            print("JSONデータが壊れています")
            
    def open_edit_dialog(self):
        dialog = EditDialog(self)
        dialog.exec()
                
if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = AddressBookApp()
    window.show()
    sys.exit(app.exec())

テーブルにデータが読み込まれました。


住所録 実行画面

7.まとめ

以上で住所録にJSONのデータが読み込まれました。

次回は、新規追加や編集などの機能を追加していきます。

お疲れさまでした。


いいなと思ったら応援しよう!