プロジェクト:名刺管理システム

これはフェーズ 2 の最終プロジェクトです。これまでのレッスンで学んだすべての知識——リスト、辞書、内包表記、文字列メソッド——を使って、実際に使えるコマンドライン名刺管理システムを構築します。このプロジェクトを完成させることで、Python のコンテナに関する中核概念を真に習得したことになります。


プロジェクト要件

ターミナルで動作する「名刺管理システム」を構築し、以下の機能をサポートします:

TEXT
1. 名刺追加 — 名前、電話番号、メールを入力
2. 全名刺表示 — 表形式で一覧表示
3. 名刺検索 — 名前で検索(曖昧検索対応)
4. 名刺修正 — 名前で情報を更新
5. 名刺削除 — 名前で削除
6. 統計表示 — 総数、メールドメイン統計
7. プログラム終了

データはリスト+辞書で保存します:すべての名刺をリストに、各名刺を辞書として格納します。


1. データ構造設計

例:名刺データ構造

PYTHON
# 各カードは辞書
# すべてのカードはリストに保存
[
    {"name": "Zhang San", "phone": "13800138000", "email": "zhangsan@example.com"},
    {"name": "Li Si", "phone": "13900139000", "email": "lisi@test.com"},
]
▶ 試してみよう

2. 完全なコード

このファイルを card_manager.py として保存し、直接実行します:

例:名刺管理システム

PYTHON
# ====== 名刺管理システム ======

# データ保存
cards = []

def show_menu():
    """メインメニューを表示"""
    print("\n" + "=" * 35)
    print("Business Card Management System")
    print("=" * 35)
    print("1. Add Card")
    print("2. List All Cards")
    print("3. Search Cards")
    print("4. Modify Card")
    print("5. Delete Card")
    print("6. Statistics")
    print("7. Exit")
    print("=" * 35)


def add_card():
    """名刺を追加"""
    print("\n--- Add Card ---")
    name = input("Name: ").strip()
    if not name:
        print("Name cannot be empty!")
        return

    phone = input("Phone: ").strip()
    email = input("Email: ").strip()

    # 重複する名前をチェック
    for card in cards:
        if card["name"] == name:
            print(f"A card named {name} already exists. Cannot add duplicate.")
            return

    cards.append({"name": name, "phone": phone, "email": email})
    print(f"Card for {name} added.")


def list_cards():
    """すべての名刺を表示"""
    if not cards:
        print("\nNo cards yet.")
        return

    print("\n--- All Cards ---")
    print(f"{'#':<4}{'Name':<10}{'Phone':<15}{'Email':<25}")
    print("-" * 54)

    for i, card in enumerate(cards, 1):
        print(f"{i:<4}{card['name']:<10}{card['phone']:<15}{card['email']:<25}")

    print(f"\nTotal: {len(cards)} cards")


def search_card():
    """名刺を検索(曖昧検索対応)"""
    if not cards:
        print("\nNo cards yet.")
        return

    keyword = input("\nEnter name to search (partial match supported): ").strip()
    if not keyword:
        print("Please enter a keyword!")
        return

    # リスト内包表記 + in で曖昧検索
    results = [card for card in cards if keyword in card["name"]]

    if not results:
        print(f"No cards found matching '{keyword}'.")
        return

    print(f"\n--- Found {len(results)} cards ---")
    for card in results:
        print(f"Name: {card['name']}")
        print(f"Phone: {card['phone']}")
        print(f"Email: {card['email']}")
        print("-" * 20)


def modify_card():
    """名刺を修正"""
    if not cards:
        print("\nNo cards yet.")
        return

    name = input("\nEnter the name to modify: ").strip()
    for card in cards:
        if card["name"] == name:
            print(f"Found card for {name}. Enter new info (press Enter to keep current):")

            new_name = input(f"Name ({card['name']}): ").strip()
            new_phone = input(f"Phone ({card['phone']}): ").strip()
            new_email = input(f"Email ({card['email']}): ").strip()

            if new_name:
                card["name"] = new_name
            if new_phone:
                card["phone"] = new_phone
            if new_email:
                card["email"] = new_email

            print(f"Card for {name} has been updated.")
            return

    print(f"No card found for {name}.")


def delete_card():
    """名刺を削除"""
    if not cards:
        print("\nNo cards yet.")
        return

    name = input("\nEnter the name to delete: ").strip()
    for i, card in enumerate(cards):
        if card["name"] == name:
            confirm = input(f"Are you sure you want to delete {name}'s card? (y/n): ")
            if confirm.lower() == "y":
                cards.pop(i)
                print(f"Card for {name} has been deleted.")
            else:
                print("Deletion cancelled.")
            return

    print(f"No card found for {name}.")


def show_stats():
    """データ統計を表示"""
    if not cards:
        print("\nNo cards yet.")
        return

    print("\n--- Statistics ---")
    print(f"Total cards: {len(cards)}")

    # メールドメインをカウント
    domains = {}
    for card in cards:
        email = card["email"]
        if "@" in email:
            domain = email.split("@")[1]
            domains[domain] = domains.get(domain, 0) + 1

    if domains:
        print("\nEmail domain distribution:")
        for domain, count in sorted(domains.items(), key=lambda x: x[1], reverse=True):
            print(f"  {domain}: {count}")

    # 電話番号とメールの有無をカウント
    has_phone = sum(1 for card in cards if card["phone"])
    has_email = sum(1 for card in cards if card["email"])
    print(f"\nWith phone: {has_phone}")
    print(f"With email: {has_email}")


# ====== メインプログラム ======
print("Welcome to the Business Card Management System!")
print("Enter the corresponding number to select a function.")

while True:
    show_menu()
    choice = input("Select option (1-7): ").strip()

    if choice == "1":
        add_card()
    elif choice == "2":
        list_cards()
    elif choice == "3":
        search_card()
    elif choice == "4":
        modify_card()
    elif choice == "5":
        delete_card()
    elif choice == "6":
        show_stats()
    elif choice == "7":
        print("Thank you for using! Goodbye!")
        break
    else:
        print("Invalid choice. Please enter a number between 1 and 7.")
▶ 試してみよう

コード設計アプローチ: 各機能は独立した関数として実装されています。メインプログラムは while ループ + if-elif でディスパッチします。これによりコード構造が明確になり、新しい機能の追加には新しい関数と elif ブランチを追加するだけで済みます。


3. 拡張の提案

プロジェクト完了後、以下の機能を追加してみてください:

機能 実装アイデア
データ永続化 json.dump() でファイルに保存、json.load() で起動時に読み込み(ファイル操作は後で学びます)
グループ管理 カードに group フィールドを追加(同僚/友人/家族)、辞書でグループ別に整理
データインポート/エクスポート join() を使って Excel で開ける CSV ファイルを出力
ソート表示 名前でソート:sorted(cards, key=lambda c: c["name"])
一括削除 カンマ区切りで複数の名前を受け付け、ループで削除

❓ よくある質問

Q なぜリスト+辞書を使うのですか?ファイル操作を直接使わないのですか?
A このプロジェクトはコンテナ操作に焦点を当てています——リストがすべてのカードを保存し、辞書が個々のカードを保存します。これによりリストの CRUD と辞書のキーと値の操作を練習できます。ファイル I/O(永続化)はフェーズ 3 で扱います。まずコンテナをマスターし、その後ファイル操作へ——段階的なアプローチです。
Q プログラムを閉じるとデータが失われます。どうすればよいですか?
A これは正常です——データはメモリに保存されており、プログラム終了時に解放されます。データを永続化するには、ファイル操作と JSON モジュールを学んだ後、json.dump(cards, open("data.json", "w")) を使ってディスクに保存します。
Q 曖昧検索は in キーワードを使っています。大文字小文字を区別しますか?
A はい。"Zhang" in "Zhang San" は True ですが、"zhang" in "Zhang" は False です(大文字小文字を区別)。大文字小文字を区別しない検索が必要な場合は、比較前に小文字に変換します:keyword.lower() in card["name"].lower()

📖 まとめ


📝 練習問題

  1. 基本(難易度 ⭐):名刺システムに新しい機能を追加——名前順にソートしてカードを表示(sorted()key パラメータを使用)。

  2. 中級(難易度 ⭐⭐):「グループ管理」機能を名刺システムに追加。各カードに group フィールド(「同僚」「友人」「家族」)を追加し、グループごとにカードを表示するメニューオプションを実装。

  3. 挑戦(難易度 ⭐⭐⭐):上記のコードを参照せずに、ゼロから「書籍管理システム」を書いてください。機能:書籍の追加(タイトル、著者、年)、全表示、著者検索、年でソート、著者別の書籍数カウント。リスト+辞書で保存し、すべてコマンドラインで実行。

100%