جاري تحميل المحتوى...
وصلت الآن إلى الفصل الأخير في مسار Python الحالي. بدل إضافة موضوع كبير جديد، ستجمع المفاهيم التي تعلمتها في برنامج صغير يعمل من Terminal. الهدف ليس كتابة مشروع ضخم، بل ممارسة طريقة التفكير التي تحول فكرة بسيطة إلى برنامج منظم يمكن تشغيله واختباره وتحسينه.
سننشئ برنامجًا نصيًا بسيطًا لإدارة المهام باسم Text-Based Task Manager.
سيعمل البرنامج من Terminal ويسمح للمستخدم بـ:
المشروع الأساسي يعتمد على مفاهيم Python التي سبقت دراستها، ولا يحتاج إلى Web أو Database أو GUI أو API أو Third-party Packages.
قبل كتابة الكود نحدد فكرة المشروع ومتطلباته بصورة مختصرة.
اسم المشروع: مدير مهام نصي.
المشكلة: نريد الاحتفاظ بقائمة مهام، معرفة حالة كل مهمة، وتحديثها وحفظها حتى يمكن استعادتها لاحقًا.
المستخدم: شخص يتعامل مع البرنامج من Terminal عن طريق Menu بسيطة.
الحفظ: تستخدم النسخة الأساسية ملفًا نصيًا باسم tasks.txt.
استراتيجية الحفظ: نحفظ المهام عند اختيار المستخدم الخروج من البرنامج.
من الأفضل تحديد Requirements قبل البدء في كتابة الكود.
البرنامج يجب أن يستطيع:
هذه الخطوة تمنعنا من كتابة أجزاء عشوائية ثم محاولة ربطها لاحقًا.
نحتاج إلى تمثيل مهمة واحدة، ثم تجميع عدة مهام.
يمكن استخدام Dictionary بسيطة:
task = {
"title": "مراجعة الدرس",
"done": False
}
المفتاح title يحفظ وصف المهمة، والمفتاح done يحفظ حالتها.
القيمة False تعني في مشروعنا أن المهمة غير مكتملة، والقيمة True تعني أنها مكتملة.
نستخدم List لتجميع Dictionaries الخاصة بالمهام:
tasks = [
{"title": "مراجعة الدرس", "done": False},
{"title": "حل التمارين", "done": True}
]
اخترنا List لأن لدينا مجموعة مهام نريد المرور عليها وإضافة عناصر جديدة إليها، واخترنا Dictionary لأن المهمة الواحدة تحتوي أكثر من معلومة مرتبطة بها.
لا نحتاج إلى كتابة البرنامج كاملًا دفعة واحدة. يمكن البناء بهذه المراحل:
هذا الأسلوب يجعل اكتشاف الأخطاء أسهل؛ فإذا عملت كل مرحلة قبل إضافة المرحلة التالية يصبح من الأسهل معرفة أين ظهرت المشكلة.
بدل وضع كل التعليمات في مكان واحد، سنقسم المسؤوليات إلى Functions واضحة:
show_menu(): تعرض Menu.show_tasks(tasks): تعرض المهام.add_task(tasks): تضيف مهمة.complete_task(tasks): تعلم مهمة بأنها مكتملة.load_tasks(filename): تحمل المهام السابقة.save_tasks(tasks, filename): تحفظ المهام.main(): تربط أجزاء البرنامج معًا.وجود Function لكل مسؤولية يجعل البرنامج أسهل في القراءة والمراجعة والاختبار.
نبدأ بـFunction بسيطة تعرض الخيارات:
def show_menu():
print("1. عرض المهام")
print("2. إضافة مهمة")
print("3. إكمال مهمة")
print("4. خروج")
لا تحتاج Menu إلى خيارات كثيرة؛ المطلوب هو دعم الوظائف الأساسية للمشروع.
نحتاج أولًا إلى التعامل مع List فارغة، ثم المرور على المهام الموجودة.
def show_tasks(tasks):
if len(tasks) == 0:
print("لا توجد مهام")
return
for index in range(len(tasks)):
task = tasks[index]
if task["done"]:
status = "مكتملة"
else:
status = "غير مكتملة"
print(index + 1, task["title"], status)
استخدمنا range(len(tasks)) حتى نحصل على index لكل عنصر. ولأن المستخدم يتعامل عادة مع أرقام تبدأ من 1، نعرض index + 1.
أما داخل List نفسها، فما زالت الفهارس تبدأ من 0.
عند إضافة مهمة نقرأ العنوان، ثم نتحقق من أنه ليس فارغًا، ثم ننشئ Dictionary ونضيفها إلى List.
def add_task(tasks):
title = input("اكتب المهمة: ").strip()
if title == "":
print("لا يمكن أن تكون المهمة فارغة")
return
task = {
"title": title,
"done": False
}
tasks.append(task)
strip() تزيل المسافات الموجودة في بداية النص ونهايته. لذلك إذا كتب المستخدم مسافات فقط تصبح القيمة فارغة بعد التنظيف ويمكن رفضها.
المهمة الجديدة تبدأ دائمًا في النسخة الأساسية بالحالة False.
لإكمال مهمة نحتاج إلى:
int.done إلى True.def complete_task(tasks):
if len(tasks) == 0:
print("لا توجد مهام لإكمالها")
return
show_tasks(tasks)
try:
number = int(input("رقم المهمة: "))
except ValueError:
print("أدخل رقمًا صحيحًا")
return
if number < 1 or number > len(tasks):
print("رقم المهمة غير موجود")
return
index = number - 1
tasks[index]["done"] = True
print("تم إكمال المهمة")
يوجد هنا نوعان مختلفان من التحقق.
إذا كتب المستخدم مثلًا:
abc
فلن يستطيع int() تحويل النص إلى عدد صحيح، ولذلك نستخدم:
try:
number = int(input("رقم المهمة: "))
except ValueError:
print("أدخل رقمًا صحيحًا")
return
قد ينجح التحويل إلى عدد، لكن الرقم نفسه لا يشير إلى مهمة موجودة.
مثلًا إذا كانت لدينا مهمتان فقط وأدخل المستخدم 5، فالتحويل إلى int ينجح، لكن الرقم غير صالح منطقيًا.
if number < 1 or number > len(tasks):
print("رقم المهمة غير موجود")
return
إذًا:
except ValueError يعالج فشل التحويل.if تتحقق من صحة الرقم بعد نجاح التحويل.نريد حفظ كل مهمة في سطر واحد داخل tasks.txt.
سنستخدم Format بسيطًا اختاره هذا المشروع:
0|مراجعة الدرس
1|حل التمارين
في هذا Format:
0 تعني أن المهمة غير مكتملة.1 تعني أن المهمة مكتملة.| يفصل الحالة عن العنوان.هذه ليست صيغة قياسية خاصة بـPython؛ إنها صيغة نصية بسيطة اخترناها لهذا المشروع.
نفتح الملف بوضع الكتابة "w" وبترميز utf-8، ثم نحول Boolean إلى "0" أو "1".
def save_tasks(tasks, filename):
with open(filename, "w", encoding="utf-8") as file:
for task in tasks:
done_value = "1" if task["done"] else "0"
file.write(f'{done_value}|{task["title"]}\n')
استخدام with يجعل الملف يُغلق بطريقة منظمة بعد انتهاء الكتلة.
استخدام encoding="utf-8" مهم لأن عناوين المهام قد تحتوي نصًا عربيًا.
عند التشغيل نريد استعادة البيانات الموجودة في tasks.txt.
def load_tasks(filename):
tasks = []
try:
with open(filename, "r", encoding="utf-8") as file:
for line in file:
line = line.strip()
if line == "":
continue
done_value, title = line.split("|", 1)
task = {
"title": title,
"done": done_value == "1"
}
tasks.append(task)
except FileNotFoundError:
pass
return tasks
split("|", 1)كل سطر يحتوي حالتين من البيانات: قيمة الحالة ثم عنوان المهمة.
مثل:
0|مراجعة الدرس
التعبير:
line.split("|", 1)
يقسم النص عند أول | فقط.
فيصبح لدينا في المثال:
done_value = "0"title = "مراجعة الدرس"ثم نستخدم:
"done": done_value == "1"
إذا كانت القيمة "1" تصبح النتيجة Boolean بقيمة True، وإلا تكون False.
FileNotFoundErrorفي أول تشغيل قد لا يكون tasks.txt موجودًا بعد.
هذه حالة متوقعة في مشروعنا: لا توجد بيانات سابقة لتحميلها.
لذلك يستخدم load_tasks():
except FileNotFoundError:
pass
هنا استخدام pass مقصود لأننا نعرف معنى الخطأ في هذا الموضع: عدم وجود ملف سابق يعني أن البرنامج سيبدأ بـList فارغة.
هذا لا يعني أن except: pass أسلوب عام جيد لإخفاء الأخطاء. نحن نتعامل مع Exception محددة وفي سياق معروف.
بعد تجهيز Functions نحتاج Loop تستمر في عرض Menu حتى يختار المستخدم الخروج.
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":
save_tasks(tasks, filename)
print("تم حفظ المهام")
break
else:
print("اختيار غير صحيح")
تستمر while True حتى يصل البرنامج إلى break في خيار الخروج.
تستخدم Conditions لتحديد Function التي يجب تشغيلها حسب اختيار المستخدم.
main()نضع نقطة البداية الرئيسية داخل Function باسم main():
def main():
filename = "tasks.txt"
tasks = load_tasks(filename)
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":
save_tasks(tasks, filename)
print("تم حفظ المهام")
break
else:
print("اختيار غير صحيح")
عند بدء البرنامج نحمل البيانات أولًا، وبعد ذلك تدخل Main Loop.
النسخة الأساسية تستخدم Strategy واحدة واضحة:
الحفظ عند الخروج.
هذا يعني أن تغيير tasks داخل الذاكرة لا يغير tasks.txt تلقائيًا.
الملف يتحدث عندما ننفذ:
save_tasks(tasks, filename)
لذلك وضعنا الاستدعاء داخل خيار الخروج قبل break.
بعد شرح الأجزاء، هذه نسخة كاملة قابلة للتشغيل للمشروع الأساسي. لا تحتاج إلى Third-party Packages ولا تستخدم OOP.
def show_menu():
print("1. عرض المهام")
print("2. إضافة مهمة")
print("3. إكمال مهمة")
print("4. خروج")
def show_tasks(tasks):
if len(tasks) == 0:
print("لا توجد مهام")
return
for index in range(len(tasks)):
task = tasks[index]
if task["done"]:
status = "مكتملة"
else:
status = "غير مكتملة"
print(index + 1, task["title"], status)
def add_task(tasks):
title = input("اكتب المهمة: ").strip()
if title == "":
print("لا يمكن أن تكون المهمة فارغة")
return
task = {
"title": title,
"done": False
}
tasks.append(task)
def complete_task(tasks):
if len(tasks) == 0:
print("لا توجد مهام لإكمالها")
return
show_tasks(tasks)
try:
number = int(input("رقم المهمة: "))
except ValueError:
print("أدخل رقمًا صحيحًا")
return
if number < 1 or number > len(tasks):
print("رقم المهمة غير موجود")
return
index = number - 1
tasks[index]["done"] = True
print("تم إكمال المهمة")
def save_tasks(tasks, filename):
with open(filename, "w", encoding="utf-8") as file:
for task in tasks:
done_value = "1" if task["done"] else "0"
file.write(f'{done_value}|{task["title"]}\n')
def load_tasks(filename):
tasks = []
try:
with open(filename, "r", encoding="utf-8") as file:
for line in file:
line = line.strip()
if line == "":
continue
done_value, title = line.split("|", 1)
task = {
"title": title,
"done": done_value == "1"
}
tasks.append(task)
except FileNotFoundError:
pass
return tasks
def main():
filename = "tasks.txt"
tasks = load_tasks(filename)
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":
save_tasks(tasks, filename)
print("تم حفظ المهام")
break
else:
print("اختيار غير صحيح")
if __name__ == "__main__":
main()
استخدام if __name__ == "__main__": هنا يساعد على تحديد نقطة تشغيل البرنامج، وهو مفهوم سبق تقديمه في فصل Modules. ليس محور المشروع الحالي.
تفترض النسخة الأساسية أن tasks.txt، إذا كان موجودًا، كُتب بواسطة البرنامج نفسه بالFormat المتوقع. معالجة الملفات التالفة يدويًا ليست هدف هذا المشروع.
بعد كتابة المشروع لا نكتفي بقراءة الكود. يجب تشغيله وتجربة الحالات المهمة.
يمكن تنفيذ سيناريو بسيط:
tasks.txt.مراجعة Python.من المفيد كتابة Test Cases قبل اعتبار المشروع منتهيًا.
| الحالة | ما الذي نختبره؟ | النتيجة المتوقعة |
|---|---|---|
| أول تشغيل بدون ملف | FileNotFoundError |
يبدأ البرنامج بـList فارغة. |
| إضافة مهمة صحيحة | add_task() |
تضاف المهمة بحالة غير مكتملة. |
| إضافة أكثر من مهمة | List | تبقى كل المهام موجودة. |
| عرض المهام | show_tasks() |
يظهر رقم وعنوان وحالة كل مهمة. |
| إدخال رقم صحيح | complete_task() |
تتحول المهمة المختارة إلى مكتملة. |
إدخال abc بدل رقم |
ValueError |
تظهر رسالة تطلب رقمًا صحيحًا. |
| إدخال رقم خارج النطاق | Logical Validation | تظهر رسالة أن المهمة غير موجودة. |
| الحفظ والخروج | save_tasks() |
تكتب البيانات إلى tasks.txt. |
| تشغيل البرنامج مرة أخرى | load_tasks() |
تعود البيانات المحفوظة. |
لسنا بحاجة إلى دورة Testing كاملة، لكن من المفيد التفكير في أكثر من نوع من الحالات.
إضافة مهمة بعنوان صحيح مثل:
حل التمارين
كتابة:
abc
عندما يطلب البرنامج رقم مهمة.
إذا كانت لدينا ثلاث مهام، فمن المفيد تجربة اختيار أول مهمة 1 وآخر مهمة 3.
هذه الحالات تساعد على اكتشاف أخطاء قد لا تظهر عند تجربة مسار واحد فقط.
إذا اختار المستخدم المهمة رقم 1، فإن index داخل List هو 0.
لذلك نستخدم:
index = number - 1
ValueError وعدم فحص النطاقنجاح int() لا يعني أن الرقم يشير إلى مهمة موجودة.
الحفظ يحدث فقط عندما تستدعي النسخة الأساسية save_tasks().
False عند محاولة إكمال المهمةإذا كان الهدف تعليم المهمة بأنها مكتملة، فالتغيير الصحيح هو:
tasks[index]["done"] = True
استخدام False هنا سيكون Logical Error؛ الكود يعمل لكنه يحقق سلوكًا مخالفًا للهدف.
لأن البيانات قد تحتوي على العربية، نستخدم encoding="utf-8" في القراءة والكتابة.
من الأسهل بناء Function ثم تجربتها، وبعد ذلك إضافة الجزء التالي.
بعد نجاح البرنامج، راجع هذه الأسئلة:
with؟utf-8؟بعد نجاح النسخة الأساسية يمكن التفكير في تحسينات صغيرة دون تغيير هدف المشروع، مثل:
هذه تحسينات اختيارية. من الأفضل أولًا التأكد من أن النسخة الأساسية تعمل بصورة صحيحة.
بعد اكتمال المشروع الأساسي فقط، يمكن استخدام ما تعلمته في Chapter 13 كطريقة إضافية لتنظيم البرنامج. مثلًا يمكن تمثيل المهمة باستخدام Class باسم Task بدل Dictionary.
هذه التوسعة ليست شرطًا للمشروع، ولا نعيد بناء البرنامج كاملًا هنا باستخدام OOP. الهدف الأساسي لهذا الفصل قابل للتنفيذ بالكامل باستخدام المفاهيم التي سبقت Chapter 13.
try وexcept ValueError لمعالجة فشل التحويل.FileNotFoundError في أول تشغيل.بهذا تكون قد أكملت أساسيات مسار Python الحالي، ولديك الآن أساس يجمع فهم اللغة مع القدرة على تحويل مشكلة صغيرة إلى برنامج منظم يمكن تطويره واختباره وتحسينه.
نشر في ١٢ أغسطس ٢٠٢٦