تخطَّ إلى المحتوى

مشروع Python: دفتر جهات الاتصال

الدرس 25 من 25 · Python

وصلت إلى الدرس الأخير في كورس Python! في مشروع لعبة التخمين كان البرنامج ينسى كل شيء عند إغلاقه. هذه المرة سنبني برنامجاً يتذكر: دفتر جهات اتصال تضيف إليه الأسماء والأرقام، وتبحث فيه، وتحذف منه، وتجد بياناتك محفوظة كلما فتحته. سنستخدم الدوال والقواميس والملفات و JSON والتعامل مع الأخطاء معاً.

قبل كتابة أي كود، المبرمج الجيد يحدد شكل البيانات. كل جهة اتصال قاموس فيه اسم ورقم، والدفتر كله قائمة من هذه القواميس:

example = [
{"name": "سارة", "phone": "01001234567"},
{"name": "خالد", "phone": "01229998877"},
]

لاحظ أن الرقم نص وليس عدداً: أرقام الهواتف تبدأ بصفر، ولا نجري عليها عمليات حسابية.

وسنقسّم البرنامج إلى دوال صغيرة، كل دالة لها مهمة واحدة. هكذا تستطيع تجربة كل جزء وحده قبل أن تجمعها. أنشئ ملفاً اسمه contacts.py واكتب فيه الخطوات التالية بالترتيب.

الخطوة 1: التحميل والحفظ

Section titled “الخطوة 1: التحميل والحفظ”

نبدأ بدالتين: واحدة تقرأ الدفتر من ملف JSON، وأخرى تحفظه فيه:

contacts.py
import json
FILE_NAME = "contacts.json"
def load_contacts():
try:
with open(FILE_NAME, encoding="utf-8") as file:
return json.load(file)
except FileNotFoundError:
return []
def save_contacts(contacts):
with open(FILE_NAME, "w", encoding="utf-8") as file:
json.dump(contacts, file, ensure_ascii=False, indent=2)

في أول تشغيل لن يكون الملف موجوداً، فتلتقط load_contacts الخطأ FileNotFoundError وتعيد قائمة فارغة بدلاً من أن ينهار البرنامج. واسم الملف محفوظ في متغير بحروف كبيرة FILE_NAME، وهذه عادة المبرمجين مع القيم الثابتة التي لا تتغير.

لنجرّب:

contacts = load_contacts()
print(contacts)
الناتج
[]

لا نريد أن يدخل الدفتر اسم فارغ، أو رقم فيه حروف، أو اسم مكرر. لذلك تتحقق دالة الإضافة من البيانات أولاً. ونحتاج قبلها دالة مساعدة تبحث عن جهة باسمها:

contacts.py
def find_contact(contacts, name):
for contact in contacts:
if contact["name"] == name:
return contact
return None
def add_contact(contacts, name, phone):
if name == "" or not phone.isdigit():
print("بيانات غير صحيحة: الاسم مطلوب، والرقم أرقام فقط")
return False
if find_contact(contacts, name) is not None:
print("هذا الاسم موجود من قبل")
return False
contacts.append({"name": name, "phone": phone})
save_contacts(contacts)
return True

التابع isdigit يعيد True إذا كان النص كله أرقاماً. والدالة تعيد True عند نجاح الإضافة و False عند فشلها، وتحفظ الدفتر في الملف فوراً بعد كل إضافة، فلا يضيع شيء لو أُغلق البرنامج فجأة.

add_contact(contacts, "سارة", "01001234567")
add_contact(contacts, "عمر", "0111abc")
add_contact(contacts, "سارة", "01229998877")
add_contact(contacts, "خالد", "01229998877")
print(len(contacts))
الناتج
بيانات غير صحيحة: الاسم مطلوب، والرقم أرقام فقط
هذا الاسم موجود من قبل
2

من أربع محاولات نجحت اثنتان فقط، كما نريد تماماً.

دالة العرض ترتّب الجهات أبجدياً بـ sorted و lambda. ودالة البحث تستخدم الصيغة المختصرة لتعيد كل جهة يظهر النص المطلوب في اسمها أو في رقمها:

contacts.py
def show_contacts(contacts):
if not contacts:
print("لا توجد جهات اتصال")
return
for contact in sorted(contacts, key=lambda c: c["name"]):
print(f"{contact['name']}: {contact['phone']}")
def search_contacts(contacts, text):
return [c for c in contacts if text in c["name"] or text in c["phone"]]
show_contacts(contacts)
print("---")
show_contacts(search_contacts(contacts, "0100"))
print("---")
show_contacts(search_contacts(contacts, "منى"))
الناتج
خالد: 01229998877
سارة: 01001234567
---
سارة: 01001234567
---
لا توجد جهات اتصال

لاحظ كيف أعدنا استخدام show_contacts لعرض نتائج البحث. دالة البحث تعيد قائمة ولا تطبع شيئاً، وهذا يجعلها صالحة للاستخدام في أي مكان.

contacts.py
def delete_contact(contacts, name):
contact = find_contact(contacts, name)
if contact is None:
return False
contacts.remove(contact)
save_contacts(contacts)
return True
print(delete_contact(contacts, "خالد"))
print(delete_contact(contacts, "خالد"))
print(load_contacts())
الناتج
True
False
[{'name': 'سارة', 'phone': '01001234567'}]

الحذف الأول نجح، والثاني فشل لأن الاسم لم يعد موجوداً. والسطر الأخير قرأ الملف من جديد ليثبت أن التغييرات حُفظت فعلاً على القرص.

الخطوة 5: القائمة الرئيسية

Section titled “الخطوة 5: القائمة الرئيسية”

بقي أن نربط الدوال بالمستخدم: حلقة while True تعرض الاختيارات، وتنفّذ ما يطلبه حتى يختار الخروج. أضف هذا في آخر الملف:

contacts.py
def main():
contacts = load_contacts()
print(f"دفتر جهات الاتصال ({len(contacts)} جهة محفوظة)")
while True:
print()
print("1) عرض الكل 2) إضافة 3) بحث 4) حذف 5) خروج")
choice = input("اختيارك: ").strip()
if choice == "1":
show_contacts(contacts)
elif choice == "2":
name = input("الاسم: ").strip()
phone = input("الرقم: ").strip()
if add_contact(contacts, name, phone):
print("تمت الإضافة")
elif choice == "3":
text = input("ابحث عن: ").strip()
show_contacts(search_contacts(contacts, text))
elif choice == "4":
name = input("الاسم المراد حذفه: ").strip()
if delete_contact(contacts, name):
print("تم الحذف")
else:
print("لم أجد هذا الاسم")
elif choice == "5":
print("إلى اللقاء!")
break
else:
print("اختيار غير معروف، اكتب رقماً من 1 إلى 5")
if __name__ == "__main__":
main()

التابع strip يحذف المسافات الزائدة التي قد يكتبها المستخدم بالخطأ. والشرط الأخير if __name__ == "__main__": تعرفه من درس الوحدات.

شغّل البرنامج بالأمر python contacts.py. وهذه جلسة تجربة:

الناتج
دفتر جهات الاتصال (0 جهة محفوظة)
1) عرض الكل 2) إضافة 3) بحث 4) حذف 5) خروج
اختيارك: 2
الاسم: سارة
الرقم: 01001234567
تمت الإضافة
1) عرض الكل 2) إضافة 3) بحث 4) حذف 5) خروج
اختيارك: 2
الاسم: عمر
الرقم: 0111abc
بيانات غير صحيحة: الاسم مطلوب، والرقم أرقام فقط
1) عرض الكل 2) إضافة 3) بحث 4) حذف 5) خروج
اختيارك: 2
الاسم: خالد
الرقم: 01229998877
تمت الإضافة
1) عرض الكل 2) إضافة 3) بحث 4) حذف 5) خروج
اختيارك: 1
خالد: 01229998877
سارة: 01001234567
1) عرض الكل 2) إضافة 3) بحث 4) حذف 5) خروج
اختيارك: 3
ابحث عن: 0122
خالد: 01229998877
1) عرض الكل 2) إضافة 3) بحث 4) حذف 5) خروج
اختيارك: 4
الاسم المراد حذفه: منى
لم أجد هذا الاسم
1) عرض الكل 2) إضافة 3) بحث 4) حذف 5) خروج
اختيارك: 9
اختيار غير معروف، اكتب رقماً من 1 إلى 5
1) عرض الكل 2) إضافة 3) بحث 4) حذف 5) خروج
اختيارك: 5
إلى اللقاء!

والآن شغّله مرة ثانية، وستجد أن الدفتر يتذكر ما أضفته:

الناتج
دفتر جهات الاتصال (2 جهة محفوظة)
1) عرض الكل 2) إضافة 3) بحث 4) حذف 5) خروج
اختيارك: 1
خالد: 01229998877
سارة: 01001234567
1) عرض الكل 2) إضافة 3) بحث 4) حذف 5) خروج
اختيارك: 5
إلى اللقاء!

جرّب بنفسك: طوّر الدفتر

Section titled “جرّب بنفسك: طوّر الدفتر”
  1. تعديل رقم: أضف اختياراً سادساً يغيّر رقم جهة موجودة.
  2. بيانات إضافية: أضف لكل جهة بريداً إلكترونياً أو ملاحظة.
  3. تأكيد الحذف: اسأل «هل أنت متأكد؟» قبل الحذف.
  4. استخدم كلاساً: حوّل الدفتر إلى كلاس ContactBook خصائصه القائمة واسم الملف، وتوابعه add و search و delete.
  • نسيان الحفظ بعد التغيير: إذا عدّلت القائمة ولم تستدعِ save_contacts، فسيضيع التعديل عند الإغلاق.
  • حفظ الرقم كعدد: int("0100") تعطي 100 ويختفي الصفر الأول. أرقام الهواتف نصوص.
  • الحذف من القائمة أثناء المرور عليها: يسبب تخطي عناصر. لذلك بحثنا عن الجهة أولاً، ثم حذفناها خارج الحلقة.
  • خلط الطباعة بالحساب: دالة تحسب وتطبع معاً يصعب إعادة استخدامها. اجعل الدالة تعيد النتيجة، ودع الطباعة لمن يستدعيها.

سؤال

لماذا تعيد load_contacts قائمة فارغة عند FileNotFoundError؟

سؤال

لماذا نحفظ رقم الهاتف كنص وليس كعدد؟

  • حدّد شكل البيانات أولاً: هنا قائمة قواميس.
  • قسّم البرنامج إلى دوال صغيرة، وجرّب كل واحدة وحدها.
  • احفظ في ملف JSON بعد كل تغيير، وحمّل منه عند بداية التشغيل.
  • تحقق من إدخال المستخدم قبل أن تحفظه.
  • اجعل الدوال تعيد قيماً، واترك الطباعة والإدخال للقائمة الرئيسية.

أنهيت كورس Python كاملاً! بدأت من print، ووصلت إلى الكلاسات والملفات وبرنامج حقيقي يحفظ بياناته. هذه الأساسيات تُبنى عليها كل مجالات Python.

اقتراحات للخطوة التالية:

  • ابنِ مشروعاً من أفكارك: مدير مصروفات، أو اختبار أسئلة يحفظ أفضل نتيجة، أو قائمة مهام.
  • ادخل عالم تعلّم الآلة و**الذكاء الاصطناعي**، فـ Python هي بوابتهما.
  • تعلّم SQL لتخزين البيانات الكبيرة في قواعد بيانات بدلاً من الملفات.
  • جرّب JavaScript لتبني صفحات ويب تفاعلية، وسترى أن أغلب الأفكار التي تعلمتها تنتقل معك.