مشروع Python: دفتر جهات الاتصال
وصلت إلى الدرس الأخير في كورس Python! في مشروع لعبة التخمين كان البرنامج ينسى كل شيء عند إغلاقه. هذه المرة سنبني برنامجاً يتذكر: دفتر جهات اتصال تضيف إليه الأسماء والأرقام، وتبحث فيه، وتحذف منه، وتجد بياناتك محفوظة كلما فتحته. سنستخدم الدوال والقواميس والملفات و JSON والتعامل مع الأخطاء معاً.
خطة المشروع
Section titled “خطة المشروع”قبل كتابة أي كود، المبرمج الجيد يحدد شكل البيانات. كل جهة اتصال قاموس فيه اسم ورقم، والدفتر كله قائمة من هذه القواميس:
example = [ {"name": "سارة", "phone": "01001234567"}, {"name": "خالد", "phone": "01229998877"},]لاحظ أن الرقم نص وليس عدداً: أرقام الهواتف تبدأ بصفر، ولا نجري عليها عمليات حسابية.
وسنقسّم البرنامج إلى دوال صغيرة، كل دالة لها مهمة واحدة. هكذا تستطيع تجربة كل جزء وحده قبل أن تجمعها. أنشئ ملفاً اسمه contacts.py واكتب فيه الخطوات التالية بالترتيب.
الخطوة 1: التحميل والحفظ
Section titled “الخطوة 1: التحميل والحفظ”نبدأ بدالتين: واحدة تقرأ الدفتر من ملف JSON، وأخرى تحفظه فيه:
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)[]الخطوة 2: الإضافة
Section titled “الخطوة 2: الإضافة”لا نريد أن يدخل الدفتر اسم فارغ، أو رقم فيه حروف، أو اسم مكرر. لذلك تتحقق دالة الإضافة من البيانات أولاً. ونحتاج قبلها دالة مساعدة تبحث عن جهة باسمها:
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من أربع محاولات نجحت اثنتان فقط، كما نريد تماماً.
الخطوة 3: العرض والبحث
Section titled “الخطوة 3: العرض والبحث”دالة العرض ترتّب الجهات أبجدياً بـ sorted و lambda. ودالة البحث تستخدم الصيغة المختصرة لتعيد كل جهة يظهر النص المطلوب في اسمها أو في رقمها:
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 لعرض نتائج البحث. دالة البحث تعيد قائمة ولا تطبع شيئاً، وهذا يجعلها صالحة للاستخدام في أي مكان.
الخطوة 4: الحذف
Section titled “الخطوة 4: الحذف”def delete_contact(contacts, name): contact = find_contact(contacts, name) if contact is None: return False contacts.remove(contact) save_contacts(contacts) return Trueprint(delete_contact(contacts, "خالد"))print(delete_contact(contacts, "خالد"))print(load_contacts())TrueFalse[{'name': 'سارة', 'phone': '01001234567'}]الحذف الأول نجح، والثاني فشل لأن الاسم لم يعد موجوداً. والسطر الأخير قرأ الملف من جديد ليثبت أن التغييرات حُفظت فعلاً على القرص.
الخطوة 5: القائمة الرئيسية
Section titled “الخطوة 5: القائمة الرئيسية”بقي أن نربط الدوال بالمستخدم: حلقة while True تعرض الاختيارات، وتنفّذ ما يطلبه حتى يختار الخروج. أضف هذا في آخر الملف:
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 “جرّب بنفسك: طوّر الدفتر”- تعديل رقم: أضف اختياراً سادساً يغيّر رقم جهة موجودة.
- بيانات إضافية: أضف لكل جهة بريداً إلكترونياً أو ملاحظة.
- تأكيد الحذف: اسأل «هل أنت متأكد؟» قبل الحذف.
- استخدم كلاساً: حوّل الدفتر إلى كلاس
ContactBookخصائصه القائمة واسم الملف، وتوابعهaddوsearchوdelete.
أخطاء شائعة
Section titled “أخطاء شائعة”- نسيان الحفظ بعد التغيير: إذا عدّلت القائمة ولم تستدعِ
save_contacts، فسيضيع التعديل عند الإغلاق. - حفظ الرقم كعدد:
int("0100")تعطي 100 ويختفي الصفر الأول. أرقام الهواتف نصوص. - الحذف من القائمة أثناء المرور عليها: يسبب تخطي عناصر. لذلك بحثنا عن الجهة أولاً، ثم حذفناها خارج الحلقة.
- خلط الطباعة بالحساب: دالة تحسب وتطبع معاً يصعب إعادة استخدامها. اجعل الدالة تعيد النتيجة، ودع الطباعة لمن يستدعيها.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
لماذا تعيد load_contacts قائمة فارغة عند FileNotFoundError؟
في أول تشغيل لم يُحفظ شيء بعد، فغياب الملف حالة طبيعية وليس خطأً يوقف البرنامج.
سؤال
لماذا نحفظ رقم الهاتف كنص وليس كعدد؟
تحويل الرقم إلى عدد يحذف الأصفار من أوله، ورقم الهاتف معرّف وليس كمية نحسب بها.
الخلاصة
Section titled “الخلاصة”- حدّد شكل البيانات أولاً: هنا قائمة قواميس.
- قسّم البرنامج إلى دوال صغيرة، وجرّب كل واحدة وحدها.
- احفظ في ملف JSON بعد كل تغيير، وحمّل منه عند بداية التشغيل.
- تحقق من إدخال المستخدم قبل أن تحفظه.
- اجعل الدوال تعيد قيماً، واترك الطباعة والإدخال للقائمة الرئيسية.
مبروك! ماذا بعد؟
Section titled “مبروك! ماذا بعد؟”أنهيت كورس Python كاملاً! بدأت من print، ووصلت إلى الكلاسات والملفات وبرنامج حقيقي يحفظ بياناته. هذه الأساسيات تُبنى عليها كل مجالات Python.
اقتراحات للخطوة التالية:
- ابنِ مشروعاً من أفكارك: مدير مصروفات، أو اختبار أسئلة يحفظ أفضل نتيجة، أو قائمة مهام.
- ادخل عالم تعلّم الآلة و**الذكاء الاصطناعي**، فـ Python هي بوابتهما.
- تعلّم SQL لتخزين البيانات الكبيرة في قواعد بيانات بدلاً من الملفات.
- جرّب JavaScript لتبني صفحات ويب تفاعلية، وسترى أن أغلب الأفكار التي تعلمتها تنتقل معك.