تمرین و پروژه‌های پایتون

ساخت لیست کارها با پایتون؛ پروژه کامل Todo List با ذخیره در JSON

ساخت لیست کارها با پایتون

🗓 آخرین به‌روزرسانی:

ساخت لیست کارها با پایتون یکی از بهترین پروژه‌ها برای عبور از تمرین‌های خیلی ساده و رسیدن به برنامه‌ای است که واقعاً داده را نگه می‌دارد و بین اجراهای مختلف از بین نمی‌رود. در این آموزش یک Todo List کامل می‌سازیم که بتواند کار جدید ثبت کند، فهرست کارها را نمایش دهد، وضعیت یک کار را به انجام‌شده تغییر دهد، کارها را حذف کند و همه اطلاعات را داخل فایل JSON ذخیره کند.

این مقاله نهمین پروژه فاز اول دسته تمرین و پروژه‌های پایتون است. در پروژه‌های قبلی بیشتر با ورودی، شرط، حلقه، توابع و محاسبات کار کردیم. حالا یک مرحله جلوتر می‌رویم و مفهوم ماندگاری داده را هم وارد پروژه می‌کنیم. اگر با لیست‌ها آشنایی کافی ندارید، مقاله لیست‌ها در پایتون و برای ذخیره‌سازی داده مقاله کار با فایل‌های JSON در پایتون را مرور کنید.

در این پروژه چه چیزی می‌سازیم؟

  • افزودن کار جدید
  • نمایش همه کارها
  • نمایش وضعیت انجام‌شده یا انجام‌نشده
  • تغییر وضعیت یک کار به انجام‌شده
  • حذف کار با شناسه
  • اختصاص شناسه یکتا به هر کار
  • ذخیره اطلاعات در فایل JSON
  • بارگذاری خودکار اطلاعات هنگام اجرای برنامه
  • مدیریت فایل خراب یا نامعتبر
  • اعتبارسنجی ورودی کاربر
  • منوی تعاملی و اجرای چندباره

چرا Todo List پروژه خوبی برای یادگیری پایتون است؟

برنامه لیست کارها در ظاهر ساده است، اما چند مفهوم مهم را هم‌زمان درگیر می‌کند. هر کار یک داده ساختاریافته است، چند کار باید در یک مجموعه نگهداری شوند، وضعیت هر کار باید قابل تغییر باشد و اطلاعات پس از بستن برنامه باقی بمانند. به همین دلیل این پروژه پلی مناسب بین تمرین‌های آموزشی و برنامه‌های واقعی‌تر است.

در نسخه‌ای که می‌سازیم، هر Task چنین ساختاری دارد:

{
    "id": 1,
    "title": "مطالعه پایتون",
    "done": False
}

هر کار سه ویژگی دارد: شناسه، عنوان و وضعیت انجام‌شدن. این ساختار به‌راحتی داخل JSON ذخیره می‌شود و بعداً قابلیت توسعه هم دارد.

ساخت فایل پروژه

یک فایل با نام زیر بسازید:

todo_list.py

پس از اجرای برنامه، فایل دیگری با نام tasks.json به‌صورت خودکار ساخته می‌شود. نیازی نیست آن را از قبل ایجاد کنید.

مرحله اول: وارد کردن ماژول‌های مورد نیاز

import json
from pathlib import Path

از json برای خواندن و نوشتن داده‌ها و از Path برای مدیریت مسیر فایل استفاده می‌کنیم. اگر با ماژول‌ها آشنا نیستید، آموزش ماژول‌ها و پکیج‌ها در پایتون می‌تواند مفید باشد.

مرحله دوم: مشخص کردن فایل ذخیره‌سازی

TASKS_FILE = Path("tasks.json")

این فایل در همان پوشه‌ای ساخته می‌شود که برنامه را اجرا می‌کنید.

مرحله سوم: بارگذاری کارها از JSON

اگر فایل وجود نداشته باشد، برنامه باید با یک لیست خالی شروع شود. اگر فایل خراب باشد نیز نباید کل برنامه متوقف شود.

def load_tasks():
    if not TASKS_FILE.exists():
        return []

    try:
        with TASKS_FILE.open(
            "r",
            encoding="utf-8",
        ) as file:
            data = json.load(file)
    except (
        json.JSONDecodeError,
        OSError,
    ):
        return []

    if not isinstance(data, list):
        return []

    return data

اگر فایل موجود نباشد، مقدار [] برگردانده می‌شود. اگر JSON نامعتبر باشد یا خطای خواندن فایل رخ دهد نیز برنامه به‌جای Crash شدن با لیست خالی ادامه می‌دهد. برای درک بهتر این بخش می‌توانید مقاله مدیریت خطا در پایتون را مطالعه کنید.

مرحله چهارم: ذخیره کارها

def save_tasks(tasks):
    with TASKS_FILE.open(
        "w",
        encoding="utf-8",
    ) as file:
        json.dump(
            tasks,
            file,
            ensure_ascii=False,
            indent=2,
        )

گزینه ensure_ascii=False باعث می‌شود متن فارسی داخل فایل JSON به‌صورت خوانا ذخیره شود. indent=2 هم ساختار فایل را مرتب‌تر می‌کند.

نمونه فایل tasks.json

[
  {
    "id": 1,
    "title": "مطالعه پایتون",
    "done": true
  },
  {
    "id": 2,
    "title": "تکمیل پروژه Todo List",
    "done": false
  }
]

مرحله پنجم: ساخت شناسه جدید

اگر فقط از طول لیست برای ID استفاده کنیم، بعد از حذف یک کار ممکن است شناسه تکراری ایجاد شود. روش بهتر این است که بزرگ‌ترین شناسه موجود را پیدا کنیم و یک واحد به آن اضافه کنیم.

def next_task_id(tasks):
    if not tasks:
        return 1

    return max(
        task.get("id", 0)
        for task in tasks
    ) + 1

مرحله ششم: افزودن کار جدید

def add_task(tasks):
    title = input(
        "عنوان کار جدید: "
    ).strip()

    if not title:
        print(
            "عنوان کار نمی‌تواند خالی باشد."
        )
        return

    task = {
        "id": next_task_id(tasks),
        "title": title,
        "done": False,
    }

    tasks.append(task)
    save_tasks(tasks)

    print("کار جدید اضافه شد.")

بعد از اضافه شدن Task، بلافاصله save_tasks() اجرا می‌شود. این نکته مهم است؛ چون اگر برنامه همان لحظه بسته شود، اطلاعات جدید از بین نمی‌روند.

مرحله هفتم: نمایش فهرست کارها

def show_tasks(tasks):
    print("\n=== لیست کارها ===")

    if not tasks:
        print(
            "هنوز کاری ثبت نشده است."
        )
        return

    for task in tasks:
        status = (
            "✓"
            if task.get("done")
            else "○"
        )

        print(
            f"{task.get('id')} - "
            f"[{status}] "
            f"{task.get('title', '')}"
        )

برای کار انجام‌شده علامت ✓ و برای کار باز علامت ○ نمایش داده می‌شود.

نمونه خروجی

=== لیست کارها ===
1 - [✓] مطالعه پایتون
2 - [○] ساخت پروژه Todo List
3 - [○] تمرین فایل JSON

مرحله هشتم: پیدا کردن کار با شناسه

برای تکمیل یا حذف Task باید بتوانیم آن را با ID پیدا کنیم.

def get_task_by_id(
    tasks,
    task_id,
):
    for task in tasks:
        if task.get("id") == task_id:
            return task

    return None

مرحله نهم: دریافت شناسه معتبر

def get_task_id(message):
    value = input(message).strip()

    try:
        return int(value)
    except ValueError:
        print(
            "شناسه باید یک عدد صحیح باشد."
        )
        return None

این تابع از ورود مستقیم متن به منطق اصلی جلوگیری می‌کند.

مرحله دهم: علامت‌زدن یک کار به‌عنوان انجام‌شده

def complete_task(tasks):
    show_tasks(tasks)

    if not tasks:
        return

    task_id = get_task_id(
        "شناسه کار انجام‌شده: "
    )

    if task_id is None:
        return

    task = get_task_by_id(
        tasks,
        task_id,
    )

    if task is None:
        print(
            "کاری با این شناسه پیدا نشد."
        )
        return

    task["done"] = True
    save_tasks(tasks)

    print(
        "وضعیت کار به انجام‌شده تغییر کرد."
    )

مرحله یازدهم: حذف کار

def delete_task(tasks):
    show_tasks(tasks)

    if not tasks:
        return

    task_id = get_task_id(
        "شناسه کار برای حذف: "
    )

    if task_id is None:
        return

    task = get_task_by_id(
        tasks,
        task_id,
    )

    if task is None:
        print(
            "کاری با این شناسه پیدا نشد."
        )
        return

    tasks.remove(task)
    save_tasks(tasks)

    print("کار حذف شد.")

مرحله دوازدهم: ساخت منوی اصلی

def show_menu():
    print("\n=== Todo List با پایتون ===")
    print("1) نمایش کارها")
    print("2) افزودن کار")
    print("3) انجام‌شده")
    print("4) حذف کار")
    print("0) خروج")

تابع main

def main():
    tasks = load_tasks()

    while True:
        show_menu()

        choice = input(
            "انتخاب شما: "
        ).strip()

        if choice == "1":
            show_tasks(tasks)
        elif choice == "2":
            add_task(tasks)
        elif choice == "3":
            complete_task(tasks)
        elif choice == "4":
            delete_task(tasks)
        elif choice == "0":
            print("برنامه پایان یافت.")
            break
        else:
            print("گزینه نامعتبر است.")

کد کامل پروژه Todo List با پایتون

import json
from pathlib import Path

TASKS_FILE = Path("tasks.json")


def load_tasks():
    if not TASKS_FILE.exists():
        return []

    try:
        with TASKS_FILE.open(
            "r",
            encoding="utf-8",
        ) as file:
            data = json.load(file)
    except (
        json.JSONDecodeError,
        OSError,
    ):
        return []

    if not isinstance(data, list):
        return []

    return data


def save_tasks(tasks):
    with TASKS_FILE.open(
        "w",
        encoding="utf-8",
    ) as file:
        json.dump(
            tasks,
            file,
            ensure_ascii=False,
            indent=2,
        )


def next_task_id(tasks):
    if not tasks:
        return 1

    return max(
        task.get("id", 0)
        for task in tasks
    ) + 1


def add_task(tasks):
    title = input(
        "عنوان کار جدید: "
    ).strip()

    if not title:
        print(
            "عنوان کار نمی‌تواند خالی باشد."
        )
        return

    task = {
        "id": next_task_id(tasks),
        "title": title,
        "done": False,
    }

    tasks.append(task)
    save_tasks(tasks)
    print("کار جدید اضافه شد.")


def show_tasks(tasks):
    print("\n=== لیست کارها ===")

    if not tasks:
        print(
            "هنوز کاری ثبت نشده است."
        )
        return

    for task in tasks:
        status = (
            "✓"
            if task.get("done")
            else "○"
        )

        print(
            f"{task.get('id')} - "
            f"[{status}] "
            f"{task.get('title', '')}"
        )


def get_task_by_id(
    tasks,
    task_id,
):
    for task in tasks:
        if task.get("id") == task_id:
            return task

    return None


def get_task_id(message):
    value = input(message).strip()

    try:
        return int(value)
    except ValueError:
        print(
            "شناسه باید یک عدد صحیح باشد."
        )
        return None


def complete_task(tasks):
    show_tasks(tasks)

    if not tasks:
        return

    task_id = get_task_id(
        "شناسه کار انجام‌شده: "
    )

    if task_id is None:
        return

    task = get_task_by_id(
        tasks,
        task_id,
    )

    if task is None:
        print(
            "کاری با این شناسه پیدا نشد."
        )
        return

    task["done"] = True
    save_tasks(tasks)

    print(
        "وضعیت کار به انجام‌شده تغییر کرد."
    )


def delete_task(tasks):
    show_tasks(tasks)

    if not tasks:
        return

    task_id = get_task_id(
        "شناسه کار برای حذف: "
    )

    if task_id is None:
        return

    task = get_task_by_id(
        tasks,
        task_id,
    )

    if task is None:
        print(
            "کاری با این شناسه پیدا نشد."
        )
        return

    tasks.remove(task)
    save_tasks(tasks)
    print("کار حذف شد.")


def show_menu():
    print("\n=== Todo List با پایتون ===")
    print("1) نمایش کارها")
    print("2) افزودن کار")
    print("3) انجام‌شده")
    print("4) حذف کار")
    print("0) خروج")


def main():
    tasks = load_tasks()

    while True:
        show_menu()

        choice = input(
            "انتخاب شما: "
        ).strip()

        if choice == "1":
            show_tasks(tasks)
        elif choice == "2":
            add_task(tasks)
        elif choice == "3":
            complete_task(tasks)
        elif choice == "4":
            delete_task(tasks)
        elif choice == "0":
            print("برنامه پایان یافت.")
            break
        else:
            print("گزینه نامعتبر است.")


if __name__ == "__main__":
    main()

نمونه اجرای برنامه

=== Todo List با پایتون ===
1) نمایش کارها
2) افزودن کار
3) انجام‌شده
4) حذف کار
0) خروج
انتخاب شما: 2
عنوان کار جدید: تمرین پایتون
کار جدید اضافه شد.

در اجرای بعدی برنامه، همان Task دوباره از فایل JSON خوانده می‌شود.

چرا ذخیره در JSON بهتر از یک List ساده است؟

اگر فقط از یک لیست در حافظه استفاده کنیم، با بسته شدن برنامه همه اطلاعات از بین می‌روند. JSON باعث می‌شود داده‌ها روی دیسک باقی بمانند و در اجرای بعدی دوباره بارگذاری شوند. علاوه بر این، ساختار JSON خوانا است و با List و Dictionary پایتون تطابق خوبی دارد.

نکته مهم درباره فایل خراب

در نسخه آموزشی ما، اگر فایل JSON خراب باشد، load_tasks() یک لیست خالی برمی‌گرداند. در یک برنامه حرفه‌ای‌تر بهتر است قبل از نادیده گرفتن فایل، نسخه پشتیبان ایجاد یا پیام هشدار دقیق نمایش داده شود. این تصمیم به نوع پروژه بستگی دارد.

اشتباه‌های رایج در پروژه Todo List

ذخیره نکردن بعد از هر تغییر

اگر فقط هنگام خروج فایل را ذخیره کنید، بسته شدن ناگهانی برنامه می‌تواند باعث از دست رفتن تغییرات شود.

استفاده از index به‌جای ID

شماره موقعیت یک Task در لیست با حذف عناصر تغییر می‌کند. شناسه مستقل رفتار قابل پیش‌بینی‌تری دارد.

پذیرفتن عنوان خالی

Task بدون عنوان کاربردی نیست. قبل از ذخیره باید strip() و بررسی خالی بودن انجام شود.

فرض معتبر بودن فایل JSON

ممکن است فایل دستی ویرایش یا ناقص شود. مدیریت JSONDecodeError جلوی توقف کامل برنامه را می‌گیرد.

چطور پروژه را توسعه دهیم؟

  1. ویرایش کار: امکان تغییر عنوان Task را اضافه کنید.
  2. بازگرداندن وضعیت: کار انجام‌شده را دوباره به حالت باز برگردانید.
  3. اولویت: برای هر Task اولویت کم، متوسط و زیاد تعریف کنید.
  4. تاریخ سررسید: فیلد Due Date اضافه کنید.
  5. فیلتر: فقط کارهای انجام‌شده یا انجام‌نشده را نمایش دهید.
  6. جست‌وجو: عنوان کارها را با یک عبارت جست‌وجو کنید.
  7. مرتب‌سازی: بر اساس اولویت یا شناسه مرتب کنید.
  8. تاریخ ایجاد: زمان ساخت هر Task را ذخیره کنید.
  9. نسخه GUI: با Tkinter رابط گرافیکی بسازید.
  10. نسخه دیتابیس: در مرحله پیشرفته JSON را با SQLite جایگزین کنید.

تمرین پیشنهادی: افزودن قابلیت ویرایش

تابعی با نام edit_task() بسازید که ID کار را بگیرد، Task را پیدا کند و عنوان جدید را ذخیره کند.

def edit_task(tasks):
    task_id = get_task_id(
        "شناسه کار برای ویرایش: "
    )

    if task_id is None:
        return

    task = get_task_by_id(
        tasks,
        task_id,
    )

    if task is None:
        print(
            "کاری با این شناسه پیدا نشد."
        )
        return

    new_title = input(
        "عنوان جدید: "
    ).strip()

    if not new_title:
        print("عنوان نمی‌تواند خالی باشد.")
        return

    task["title"] = new_title
    save_tasks(tasks)

این پروژه چه مفاهیمی را تمرین می‌دهد؟

  • List و Dictionary
  • ماژول json
  • pathlib و Path
  • خواندن و نوشتن فایل
  • توابع و پارامترها
  • حلقه for و while
  • شرط‌ها
  • Exception Handling
  • اعتبارسنجی ورودی
  • شناسه‌گذاری داده‌ها
  • ماندگاری اطلاعات
  • تفکیک مسئولیت‌ها در طراحی برنامه

جمع‌بندی

در این آموزش یک Todo List کامل با پایتون ساختیم که کارها را فقط در حافظه نگه نمی‌دارد، بلکه آن‌ها را در فایل JSON ذخیره می‌کند. کاربر می‌تواند Task جدید اضافه کند، فهرست را ببیند، یک کار را انجام‌شده علامت بزند و Taskهای اضافی را حذف کند.

مهم‌ترین بخش این پروژه، ترکیب چند مفهوم پایه در یک برنامه واقعی است: لیست، دیکشنری، فایل، JSON، توابع، حلقه، شرط و مدیریت خطا. بعد از اجرای نسخه اصلی، قابلیت ویرایش Task را اضافه کنید؛ این توسعه ساده نشان می‌دهد آیا ساختار کلی پروژه را به‌درستی درک کرده‌اید یا نه.

📌 همچنین بخوانید: تمرین پایتون با جواب، ساخت بازی با پایتون و Pygame و ساخت ربات با پایتون

می‌خواهی پیشرفتت ذخیره شود؟با ورود یا ثبت‌نام رایگان، درس‌های تمام‌شده را علامت بزن و درصد پیشرفتت را در حساب کاربری ببین.ورود / ثبت‌نام برای ذخیره پیشرفت
درس بعدی · پروژه‌های پایتون (10 از 19)ساخت بازی حدس کلمه با پایتون؛ پروژه کامل Hangman با random و Set ←
آموزش‌های تازه، کد آماده و رفع خطاهر هفته در کانال Dev Source؛ رایگان.
🧭 مسیر یادگیری پایتون از صفر
ماژول 7: مدیریت خطا و فایل‌ها — درس 34 از 44

برای ذخیره پیشرفت در همه دستگاه‌ها و دریافت گواهی پایان مسیر، رایگان ثبت‌نام کن یا وارد شو.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *