プロジェクト:名刺管理システム
これはフェーズ 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()。📖 まとめ
- 名刺管理システムはリストで全データを保存し、辞書で個々のレコードを保存——最も一般的なデータ整理パターン
- モジュール設計:各機能は独立した関数。メインプログラムはループでディスパッチ
enumerate()で番号付きリスト表示を生成- リスト内包表記で曖昧検索を実装
- 辞書の
get()メソッドを頻度カウントに使用 - このプロジェクトはシンプルですが、フェーズ 2 のすべての中核概念をカバー
📝 練習問題
-
基本(難易度 ⭐):名刺システムに新しい機能を追加——名前順にソートしてカードを表示(
sorted()とkeyパラメータを使用)。 -
中級(難易度 ⭐⭐):「グループ管理」機能を名刺システムに追加。各カードに
groupフィールド(「同僚」「友人」「家族」)を追加し、グループごとにカードを表示するメニューオプションを実装。 -
挑戦(難易度 ⭐⭐⭐):上記のコードを参照せずに、ゼロから「書籍管理システム」を書いてください。機能:書籍の追加(タイトル、著者、年)、全表示、著者検索、年でソート、著者別の書籍数カウント。リスト+辞書で保存し、すべてコマンドラインで実行。



