ساخت لیست کارها با پایتون؛ پروژه کامل 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 جلوی توقف کامل برنامه را میگیرد.
چطور پروژه را توسعه دهیم؟
- ویرایش کار: امکان تغییر عنوان Task را اضافه کنید.
- بازگرداندن وضعیت: کار انجامشده را دوباره به حالت باز برگردانید.
- اولویت: برای هر Task اولویت کم، متوسط و زیاد تعریف کنید.
- تاریخ سررسید: فیلد Due Date اضافه کنید.
- فیلتر: فقط کارهای انجامشده یا انجامنشده را نمایش دهید.
- جستوجو: عنوان کارها را با یک عبارت جستوجو کنید.
- مرتبسازی: بر اساس اولویت یا شناسه مرتب کنید.
- تاریخ ایجاد: زمان ساخت هر Task را ذخیره کنید.
- نسخه GUI: با Tkinter رابط گرافیکی بسازید.
- نسخه دیتابیس: در مرحله پیشرفته 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 و ساخت ربات با پایتون
برای ذخیره پیشرفت در همه دستگاهها و دریافت گواهی پایان مسیر، رایگان ثبتنام کن یا وارد شو.