الفصل الأخير: بناء مشروع متكامل – تطبيق قائمة المهام
مقدمة الفصل
لقد وصلت إلى المحطة النهائية والأكثر أهمية في رحلتك التعليمية. خلال الفصول السابقة، جمعت كل الأدوات التي تحتاجها: تعلمت عن المتغيرات، الشروط، الحلقات، الدوال، هياكل البيانات، البرمجة كائنية التوجه، والتعامل مع الملفات. الآن، حان الوقت لتفريغ صندوق الأدوات هذا واستخدام كل ما فيه لبناء شيء حقيقي وملموس.
هذا الفصل هو تتويج لكل ما تعلمته. سنقوم هنا بجمع كل المهارات التي اكتسبتها لبناء مشروع حقيقي ومتكامل من الألف إلى الياء. مشروعنا سيكون تطبيقًا لإدارة المهام (To-Do List) يعمل من سطر الأوامر. من خلال بنائه، لن تتعلم فقط كيف تجمع المفاهيم معًا، بل كيف يفكر المبرمجون المحترفون عند تحويل فكرة إلى منتج عملي.
هيا بنا نبدأ في بناء آخر وأهم مشروع في هذا الكتاب.
1. التخطيط للمشروع: التفكير قبل البرمجة
مقدمة هذا القسم
قبل أن نكتب أي سطر برمجي، أفضل الممارسات دائمًا هي التوقف والتخطيط. التخطيط الجيد يشبه رسم خريطة قبل الانطلاق في رحلة؛ فهو يمنعنا من الضياع ويوفر علينا الكثير من الوقت والجهد لاحقًا. في هذا القسم، سنجيب على سؤالين أساسيين: ماذا نريد أن يفعل برنامجنا؟ وكيف سننظم ملفاتنا لتحقيق ذلك بطريقة احترافية؟
أ. ميزات التطبيق (ماذا سيفعل؟)
أولاً، يجب أن نحدد بوضوح ما هي الوظائف التي سيقدمها تطبيقنا للمستخدم. تطبيقنا البسيط يجب أن يسمح بالوظائف الأساسية التالية:
- عرض جميع المهام الحالية.
- إضافة مهمة جديدة إلى القائمة.
- تحديث مهمة معينة واعتبارها “مُنجزة”.
- حذف مهمة لم نعد بحاجتها.
- حفظ المهام بشكل دائم في ملف، بحيث لا تُفقد عند إغلاق التطبيق.
- الخروج من التطبيق.
ب. هيكل المشروع (كيف سننظمه؟)
لدينا عدة مسؤوليات مختلفة في هذا التطبيق: هناك منطق يخص المهمة نفسها، ومنطق يخص تخزينها، ومنطق يخص التفاعل مع المستخدم. بدلاً من وضع كل هذا في ملف واحد كبير وفوضوي، سنطبق مبدأ “فصل الاهتمامات” (Separation of Concerns).
تخيل أننا نبني مطعمًا:
- هناك الشيف الذي يملك “وصفة” كل وجبة (
task.py
). هو يعرف مكونات الوجبة وكيفية تحضيرها. - هناك المخزن والثلاجة (
storage.py
). هذا النظام مسؤول عن تخزين المكونات والوجبات بشكل آمن واسترجاعها عند الحاجة. - هناك النادل وقائمة الطعام (
main.py
). هذا هو الجزء الذي يتحدث مع الزبون، يأخذ الطلبات، ويقدم له الوجبات الجاهزة.
النادل لا يحتاج أن يعرف تفاصيل الوصفة، والشيف لا يحتاج أن يعرف كيف تعمل الثلاجة. كل جزء له مسؤوليته الخاصة، وهذا يجعل المطعم منظمًا وفعالاً. سنطبق نفس المبدأ على مشروعنا:
todo_project/
│
├── task.py # (ملف الوصفة): سيحتوي على كلاس Task الذي يصف ما هي "المهمة".
├── storage.py # (ملف المخزن): سيكون مسؤولاً عن حفظ واسترجاع المهام من ملف.
└── main.py # (ملف النادل): سيكون مسؤولاً عن الواجهة والتفاعل مع المستخدم.
خاتمة هذا القسم
الآن بعد أن وضعنا خطة واضحة، نحن جاهزون للبدء في البرمجة. سنبدأ ببناء الجزء الأساسي والأكثر أهمية في تطبيقنا: “وصفة” المهمة، أو كلاس Task
.
2. بناء “مخطط” المهمة (ملف task.py
)
مقدمة هذا القسم
في هذا الجزء، سنركز فقط على تعريف “المهمة” كوحدة مستقلة. ما هي المعلومات التي يجب أن تحتوي عليها كل مهمة؟ (مثل عنوانها وحالتها). وما هي الأفعال التي يمكننا القيام بها على مهمة واحدة؟ (مثل تغيير حالتها إلى “منجزة”). كل هذا المنطق سنضعه في كلاس Task
.
الآن، في مجلد مشروعك، أنشئ ملفًا جديدًا باسم task.py
واتبع الخطوات التالية:
الخطوة 1: استيراد وحدة datetime
الهدف: نريد أن تسجل كل مهمة وقت وتاريخ إنشائها تلقائيًا. أفضل أداة لهذا هي وحدة datetime
من مكتبة بايثون القياسية.
اكتب السطر التالي في بداية ملف task.py
:
import datetime
الشرح: هذا السطر يجعل كل الأدوات المتعلقة بالوقت والتاريخ من مكتبة بايثون متاحة للاستخدام في ملفنا.
الخطوة 2: تعريف كلاس Task
الهدف: إنشاء “المخطط” (Blueprint) الذي سيتم استخدامه لإنشاء كل مهمة في تطبيقنا.
أضف السطر التالي:
class Task:
"""يمثل مهمة واحدة في قائمة المهام لدينا."""
الشرح: نستخدم الكلمة المفتاحية class
لتعريف كلاس جديد. أضفنا docstring
لشرح الغرض منه، وهي ممارسة جيدة دائمًا.
الخطوة 3: بناء المُنشئ (دالة __init__
)
الهدف: تحديد الخصائص الأولية التي يجب أن تمتلكها كل مهمة جديدة عند إنشائها.
أضف الأسطر التالية داخل كلاس Task
(تذكر المسافة البادئة):
def __init__(self, title, is_done=False):
"""يقوم بتهيئة مهمة جديدة عند إنشائها."""
self.title = title
self.is_done = is_done
self.created_at = datetime.datetime.now()
الشرح التفصيلي:
__init__
هي دالة “سحرية” خاصة جدًا، تعمل تلقائيًا في كل مرة ننشئ فيها كائنTask
جديد.self
هو أهم معامل، وهو يشير إلى الكائن (المهمة) نفسه الذي يتم إنشاؤه في تلك اللحظة. بايثون تقوم بتمريره تلقائيًا.title
هو عنوان المهمة الذي يجب أن نزوده به عند إنشائها.is_done=False
يعني أن أي مهمة جديدة ننشئها ستكون حالتها “غير منجزة” بشكل افتراضي.self.title = title
: هذا السطر يعني: “بالنسبة لهذه المهمة تحديدًا (self
)، أنشئ خاصية اسمهاtitle
وخزن فيها قيمة العنوانtitle
الذي تم تمريره”. وبالمثل لباقي الخصائص.
الخطوة 4: إضافة دالة لتحديث حالة المهمة
الهدف: توفير طريقة لتغيير حالة المهمة من “غير منجزة” إلى “منجزة”.
أضف الأسطر التالية داخل كلاس Task
:
def mark_as_done(self):
"""يضع علامة على المهمة كمنجزة."""
self.is_done = True
الشرح:
mark_as_done
هي دالة (method) بسيطة. لأنها تأخذ self
كمعامل، فهي تستطيع الوصول إلى خصائص الكائن الحالي وتعديلها. كل ما تفعله هو تغيير قيمة الخاصية self.is_done
إلى True
.
الخطوة 5: تحديد كيفية طباعة المهمة (دالة __str__
)
الهدف: عندما نستخدم print()
على كائن المهمة، نريد عرض معلومات مفيدة ومنسقة بدلاً من عنوان الذاكرة الغامض.
أضف الأسطر التالية داخل كلاس Task
:
def __str__(self):
"""تعيد تمثيلاً نصيًا سهل القراءة للمهمة."""
# نحدد أيقونة الحالة بناءً على قيمة is_done
status_icon = "✅" if self.is_done else "🔘"
# نقوم بتنسيق التاريخ لعرضه بشكل جميل
formatted_date = self.created_at.strftime("%Y-%m-%d")
# نعيد النص النهائي المنسق
return f"[{status_icon}] {self.title} (أضيفت في: {formatted_date})"
الشرح:
__str__
هي دالة سحرية أخرى يتم استدعاؤها تلقائيًا عندما نحاول تحويل الكائن إلى نص (مثلاً عند طباعته).- نستخدم تعبيرًا ثلاثيًا (اختصار لـ
if/else
) لاختيار الأيقونة المناسبة. - نعيد (
return
) نصًا منسقًا باستخدام f-string يجمع كل المعلومات معًا بشكل أنيق.
خاتمة هذا القسم
ممتاز! لقد قمنا الآن ببناء “النموذج” (Model) الخاص بتطبيقنا. لدينا مخطط واضح لكيفية إنشاء أي مهمة، وما هي خصائصها، وكيف تتصرف. الخطوة التالية هي بناء “المخزن” الذي سيقوم بحفظ هذه المهام بشكل دائم.
الجزء الثاني: بناء وحدة التخزين (ملف storage.py
)
مقدمة هذا القسم
في هذا الجزء، سنبني الوحدة المسؤولة عن الاستمرارية (Persistence). كائنات Task
التي ننشئها تعيش في ذاكرة الحاسوب المؤقتة (RAM)، وتختفي عند إغلاق البرنامج. مهمة storage.py
هي تحويل هذه الكائنات إلى صيغة نصية يمكن حفظها في ملف، واسترجاعها مرة أخرى عند تشغيل البرنامج. هذه العملية تسمى التحويل التسلسلي (Serialization).
أنشئ ملفًا جديدًا باسم storage.py
واتبع الخطوات التالية:
1. استيراد الوحدات اللازمة
الهدف: جلب الأدوات التي نحتاجها: json
لتحويل هياكل بيانات بايثون إلى نص والعكس، و datetime
للتعامل مع التواريخ، وكلاس Task
لنتمكن من إعادة بناء كائنات المهام عند تحميلها.
import json
import datetime
from task import Task
2. بناء دالة save_tasks
(التحويل التسلسلي)
الهدف: أخذ قائمة من كائنات Task
، تحويلها إلى قائمة من القواميس البسيطة، ثم حفظها في ملف JSON.
# تحديد اسم الملف كثابت لتسهيل تغييره
STORAGE_FILE = "tasks.json"
def save_tasks(tasks):
"""تحول قائمة كائنات المهام إلى قواميس وتحفظها في ملف JSON."""
list_to_save = []
# نمر على كل كائن مهمة في القائمة
for task in tasks:
# نحول كل كائن إلى قاموس
task_dict = {
"title": task.title,
"is_done": task.is_done,
"created_at": task.created_at.isoformat()
}
list_to_save.append(task_dict)
# نفتح الملف ونحفظ القائمة المحولة بداخله
with open(STORAGE_FILE, "w", encoding="utf-8") as f:
json.dump(list_to_save, f, indent=4, ensure_ascii=False)
الشرح:
- لا يمكن لـ JSON فهم كائنات بايثون المعقدة مباشرة، لكنه يفهم القواميس والقوائم. لذلك، نمر على كل كائن
task
ونستخلص بياناته في قاموسdict
بسيط. - نحول كائن التاريخ إلى نص باستخدام
.isoformat()
، وهي صيغة قياسية يمكن تحويلها مرة أخرى بسهولة. - نستخدم
json.dump
لحفظ قائمة القواميس في الملف.indent=4
تجعل الملف منظمًا، وensure_ascii=False
ضرورية لحفظ الأحرف العربية.
3. بناء دالة load_tasks
(إلغاء التحويل التسلسلي)
الهدف: قراءة البيانات من ملف JSON وتحويلها مرة أخرى إلى قائمة من كائنات Task
التي يفهمها برنامجنا.
def load_tasks():
"""تحمل المهام من ملف JSON وتعيد قائمة من كائنات المهام."""
try:
with open(STORAGE_FILE, "r", encoding="utf-8") as f:
list_of_dicts = json.load(f)
loaded_tasks = []
for task_dict in list_of_dicts:
# نعيد بناء كائن Task جديد من بيانات القاموس
task_obj = Task(task_dict['title'], task_dict['is_done'])
# نعيد تحويل نص التاريخ إلى كائن datetime
task_obj.created_at = datetime.datetime.fromisoformat(task_dict['created_at'])
loaded_tasks.append(task_obj)
return loaded_tasks
except (FileNotFoundError, json.JSONDecodeError):
# إذا لم يكن الملف موجودًا أو كان فارغًا، نعيد قائمة فارغة
return []
الشرح:
- نستخدم
try...except
للتعامل مع حالتين:FileNotFoundError
(عند أول تشغيل للبرنامج) وjson.JSONDecodeError
(إذا كان الملف فارغًا أو تالفًا). json.load
يقرأ الملف ويعيد لنا قائمة من القواميس.- داخل الحلقة، نمر على كل قاموس ونستخدم بياناته كـ “مكونات” لإنشاء كائن
Task
جديد، مع تحويل نص التاريخ مرة أخرى إلى كائنdatetime
حقيقي باستخدامfromisoformat
.
خاتمة هذا القسم
لقد قمنا ببناء نظام تخزين قوي وموثوق. أصبح لدينا الآن القدرة على حفظ واسترجاع حالة تطبيقنا بشكل دائم. الخطوة الأخيرة هي بناء الواجهة التي ستمكن المستخدم من التفاعل مع كل هذه الميزات.
الجزء الثالث: بناء الواجهة الرئيسية (ملف main.py
)
مقدمة هذا القسم
هذا هو الملف الذي سيقوم المستخدم بتشغيله. إنه “العقل المدبر” أو “لوحة التحكم” للتطبيق. وظيفته هي عرض الخيارات للمستخدم، استقبال أوامره، ثم استدعاء الأدوات المناسبة من الوحدات الأخرى (task.py
و storage.py
) لتنفيذ تلك الأوامر.
أنشئ ملفًا جديدًا باسم main.py
واتبع الخطوات التالية:
1. استيراد الأدوات اللازمة
الهدف: جلب الأدوات من الوحدات التي أنشأناها.
from storage import save_tasks, load_tasks
from task import Task
2. بناء الدوال المساعدة للواجهة
الهدف: تنظيم الكود المتكرر. بدلاً من كتابة كود عرض القائمة وعرض المهام عدة مرات، نضعهما في دوال.
def show_menu():
"""تعرض القائمة الرئيسية للمستخدم."""
print("\n--- تطبيق قائمة المهام ---")
print("1. عرض جميع المهام")
print("2. إضافة مهمة جديدة")
print("3. تحديث مهمة (إنجاز)")
print("4. حذف مهمة")
print("5. الخروج")
def view_tasks(tasks_list):
"""تعرض كل المهام في القائمة مع ترقيمها."""
print("\n--- مهامك الحالية ---")
if not tasks_list:
print("قائمة مهامك فارغة.")
else:
for i, task in enumerate(tasks_list, start=1):
print(f"{i}. {task}")
الشرح:
- دالة
show_menu
مسؤولة عن شيء واحد فقط: طباعة الخيارات المتاحة. - دالة
view_tasks
مسؤولة عن عرض قائمة المهام بشكل منسق. نستخدمenumerate
لإضافة ترقيم تلقائي يبدأ من 1.
3. بناء الدالة الرئيسية main
ومنطق التطبيق
الهدف: احتواء المنطق الرئيسي للتطبيق، بما في ذلك حلقة التشغيل التي تستقبل أوامر المستخدم وتنفذها.
def main():
"""الدالة الرئيسية التي تشغل التطبيق."""
# أول شيء نفعله هو تحميل أي مهام محفوظة من الجلسات السابقة
tasks_list = load_tasks()
# نستخدم حلقة لا نهائية لإبقاء البرنامج يعمل
while True:
show_menu()
choice = input("أدخل اختيارك (1-5): ")
if choice == '1':
# عرض كل المهام
view_tasks(tasks_list)
elif choice == '2':
# إضافة مهمة جديدة
title = input("أدخل عنوان المهمة الجديدة: ")
tasks_list.append(Task(title))
save_tasks(tasks_list) # نحفظ التغيير فورًا
print("تمت إضافة المهمة بنجاح.")
view_tasks(tasks_list) # نعرض القائمة المحدثة
elif choice == '3':
# تحديث مهمة
view_tasks(tasks_list)
try:
task_num = int(input("أدخل رقم المهمة التي تريد إنجازها: "))
if 1 <= task_num <= len(tasks_list):
tasks_list[task_num - 1].mark_as_done()
save_tasks(tasks_list)
print("تم تحديث المهمة بنجاح.")
else:
print("رقم مهمة غير صالح.")
except ValueError:
print("الرجاء إدخال رقم صحيح.")
elif choice == '4':
# حذف مهمة
view_tasks(tasks_list)
try:
task_num = int(input("أدخل رقم المهمة التي تريد حذفها: "))
if 1 <= task_num <= len(tasks_list):
tasks_list.pop(task_num - 1)
save_tasks(tasks_list)
print("تم حذف المهمة بنجاح.")
else:
print("رقم مهمة غير صالح.")
except ValueError:
print("الرجاء إدخال رقم صحيح.")
elif choice == '5':
# الخروج من البرنامج
print("إلى اللقاء!")
break
else:
print("اختيار غير صالح. الرجاء اختيار رقم من 1 إلى 5.")
# هذا السطر يضمن أن دالة main() تعمل فقط عند تشغيل هذا الملف مباشرة
if __name__ == "__main__":
main()
الشرح:
load_tasks()
: يتم استدعاؤها مرة واحدة في البداية لاستعادة حالة التطبيق.while True:
: هذه الحلقة هي “قلب” التطبيق الذي يستمر في العمل وعرض القائمة.- منطق الخيارات: كل كتلة
if/elif
مسؤولة عن خيار واحد. النمط العام هو: استقبال مدخلات إضافية من المستخدم إذا لزم الأمر، تعديل قائمةtasks_list
(بإضافة كائن، أو استدعاء دالة على كائن، أو حذف كائن)، ثم استدعاءsave_tasks
فورًا لحفظ الحالة الجديدة.
خاتمة هذا القسم
لقد قمنا الآن بتجميع كل الأجزاء معًا. لدينا نموذج بيانات قوي (Task
)، ونظام تخزين موثوق (storage
)، وواجهة مستخدم تفاعلية (main
). كل جزء يعمل بانسجام مع الآخر لبناء تطبيق متكامل.
4. تشغيل التطبيق وخاتمة الكتاب
الآن بعد أن أصبحت كل الملفات (task.py
, storage.py
, main.py
) جاهزة في نفس المجلد، كل ما عليك فعله هو فتح سطر الأوامر في هذا المجلد وتشغيل الملف الرئيسي:
python main.py
جرب إضافة مهام، عرضها، تحديثها، وحذفها. أغلق التطبيق وأعد تشغيله. ستجد أن مهامك لا تزال محفوظة!
تهانينا! لقد وصلت إلى نهاية رحلتك التعليمية. لم تعد مجرد شخص يقرأ عن البرمجة، بل أصبحت شخصًا يبني تطبيقات حقيقية، والأهم من ذلك، أنك تعلمت كيف تفكر كمبرمج محترف: كيف تخطط، وكيف تنظم، وكيف تبني. هذا المشروع هو دليل على أنك الآن تفهم كيفية دمج كل المفاهيم التي درسناها معًا في منتج عملي ومتكامل.
هذه ليست النهاية، بل هي البداية الحقيقية. عالم البرمجة واسع وممتع، والآن لديك الأساس القوي الذي يمكنك البناء عليه لاستكشاف أي مجال يثير اهتمامك. أتمنى لك كل التوفيق في رحلتك كمبرمج.