بايثون بالمثال 20: برنامج تتبع المصروفات ببايثون وحساب الميزانية خطوة بخطوة

إنشاء برنامج تتبع المصروفات وحساب الميزانية ببايثون للمبتدئين

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

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

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

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

إذا كنت تبحث عن برنامج تتبع المصروفات ببايثون أو مشروع عملي للتدرب على الملفات والدوال والقواميس، فهذا المثال يجمع هذه المهارات داخل تطبيق واحد، مع شرح طريقة حساب الميزانية الشهرية وتحليل المصروفات حسب التصنيف.

{alertInfo} الفكرة ببساطة: سنخزن كل مصروف داخل قاموس، ثم نضع جميع المصروفات داخل قائمة، ونحفظ هذه القائمة في ملف expenses.csv. بعد ذلك سنبني دوال لإضافة المصروفات وعرضها وتحليلها وربطها بميزانية شهرية.

{getToc} $title={محتوى المقال}

ماذا سنتعلم في هذا المشروع؟

  • إنشاء برنامج تتبع مصروفات بواجهة Terminal.
  • تنظيم البيانات داخل قائمة من القواميس.
  • حفظ البيانات وقراءتها من ملف CSV.
  • إضافة مصروفات جديدة مع التحقق من الإدخال.
  • عرض جميع المصروفات.
  • حساب إجمالي المصروفات.
  • تلخيص الإنفاق حسب التصنيف.
  • تحديد ميزانية شهرية وحساب المبلغ المتبقي.
  • عرض أعلى فئة إنفاق.
  • معالجة أخطاء شائعة بشكل احترافي.

كيف يعمل برنامج تتبع المصروفات؟

لفهم المشروع بسهولة، تخيل أن المستخدم سجّل مصروفًا بقيمة 25.50 وحدد التصنيف «طعام» وكتب وصفًا وتاريخًا. البرنامج لا يطبع هذه البيانات وينتهي، بل يمررها عبر عدة مراحل مترابطة.

  1. الإدخال: يقرأ البرنامج المبلغ والتصنيف والوصف والتاريخ باستخدام input().
  2. التحقق: يتأكد أن المبلغ رقم أكبر من صفر وأن الحقول المهمة ليست فارغة.
  3. التخزين المؤقت: يحول المصروف إلى قاموس ويضيفه إلى قائمة expenses.
  4. الحفظ الدائم: يكتب محتوى القائمة داخل expenses.csv حتى لا تضيع البيانات عند إغلاق البرنامج.
  5. التحليل: يستخدم القيم المحفوظة لحساب الإجمالي، وتجميع المصروفات حسب التصنيف، ومقارنة الإنفاق بالميزانية.

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

فكرة المشروع قبل كتابة الكود

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

  • amount : قيمة المصروف.
  • category : تصنيف المصروف مثل الطعام أو المواصلات.
  • description : وصف قصير.
  • date : تاريخ المصروف.

وبدلًا من حفظ كل مصروف في متغير منفصل، سنستخدم قائمة اسمها expenses، تحتوي على عدة قواميس.

expenses = [
    {
        "amount": 25.5,
        "category": "طعام",
        "description": "وجبة غداء",
        "date": "2026-08-01"
    },
    {
        "amount": 10,
        "category": "مواصلات",
        "description": "أجرة سيارة",
        "date": "2026-08-01"
    }
]

هذا الهيكل مرن جدًا، ويسمح لنا بالإضافة والحذف والقراءة والتحليل بسهولة.

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

استخدام أسماء ثابتة للمفاتيح مثل amount وcategory مهم جدًا. إذا تغير اسم المفتاح في جزء من البرنامج ولم يتغير في الأجزاء الأخرى، ستظهر أخطاء عند القراءة أو الحساب. لذلك من الأفضل تحديد بنية السجل مرة واحدة والالتزام بها في كل الدوال.

تنظيم بيانات المصروفات في بايثون داخل قائمة من القواميس وحفظها في CSV

لماذا اخترنا ملف CSV؟

اخترنا CSV لأنه بسيط وسهل القراءة والكتابة، ويمكن فتحه لاحقًا في برامج مثل Excel أو Google Sheets. كما أنه مناسب جدًا للمشاريع التعليمية والمشاريع الصغيرة.

كل سطر في الملف سيمثل مصروفًا واحدًا، بينما يمثل الصف الأول أسماء الحقول. بهذه الطريقة تبقى البيانات مفهومة حتى خارج البرنامج، ويمكن للمستخدم مراجعتها يدويًا أو نقلها إلى برنامج جداول.

في مشروع صغير مثل هذا، CSV اختيار مناسب لأنه لا يحتاج إلى قاعدة بيانات أو إعداد إضافي. لكن إذا تطور البرنامج ليحتوي على آلاف السجلات أو حسابات مستخدمين أو عمليات بحث وتعديل معقدة، فحينها تصبح قاعدة بيانات مثل SQLite خيارًا أفضل.

الخيار الفائدة
القوائم والقواميس تنظيم البيانات داخل البرنامج أثناء التشغيل.
ملف CSV حفظ البيانات بشكل دائم واستعادتها عند تشغيل البرنامج مرة أخرى.

هل يمكن استخدام هذا المشروع بدون مكتبات خارجية؟

نعم. النسخة الحالية تعتمد على مكتبات مدمجة مع بايثون مثل csv وos، لذلك لا تحتاج إلى تثبيت حزم خارجية لبدء المشروع.

أين يتم حفظ المصروفات بعد إغلاق البرنامج؟

يتم حفظها داخل ملف expenses.csv الموجود بجانب ملف البرنامج، ثم تقرأه دالة التحميل عند التشغيل التالي.

ما الفرق بين قائمة expenses وملف expenses.csv؟

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

هل يمكن إضافة التاريخ تلقائيًا بدل كتابته يدويًا؟

نعم. يمكن تطوير المشروع لاحقًا باستخدام مكتبة datetime للحصول على تاريخ اليوم تلقائيًا، لكننا أبقينا الإدخال يدويًا في النسخة التعليمية حتى تكون خطوات تخزين البيانات واضحة.

الملفات التي سنستخدمها

مشروعنا بسيط، ويمكن أن يبدأ بملف واحد فقط:

expense_tracker.py

والبرنامج سيُنشئ ملف البيانات:

expenses.csv

استيراد المكتبات المطلوبة

سنستخدم مكتبتين من مكتبات بايثون القياسية:

  • csv للتعامل مع ملفات CSV.
  • os للتحقق من وجود الملف.
import csv
import os

تجهيز المتغيرات الأساسية

FILE_NAME = "expenses.csv"
expenses = []
monthly_budget = 0.0

هنا عرفنا:

  • FILE_NAME اسم ملف البيانات.
  • expenses القائمة التي ستحتوي على جميع المصروفات.
  • monthly_budget الميزانية الشهرية التي سيحددها المستخدم.

إنشاء دالة تحميل المصروفات من CSV

عند تشغيل البرنامج نحتاج إلى قراءة الملف، وإذا لم يكن موجودًا نبدأ بقائمة فارغة.

def load_expenses():
    global expenses

    if not os.path.exists(FILE_NAME):
        expenses = []
        return

    with open(
        FILE_NAME,
        mode="r",
        newline="",
        encoding="utf-8"
    ) as file:
        reader = csv.DictReader(file)
        expenses = []

        for row in reader:
            row["amount"] = float(row["amount"])
            expenses.append(row)

لنقرأ الدالة من الأعلى إلى الأسفل. أولًا نستخدم os.path.exists() للتأكد من وجود الملف. إذا كان المستخدم يشغّل البرنامج لأول مرة فلن يكون الملف موجودًا، ولذلك نبدأ بقائمة فارغة بدل إظهار خطأ.

إذا وُجد الملف، نفتحُه بترميز utf-8 حتى تبقى النصوص العربية سليمة، ثم نستخدم csv.DictReader. هذه الأداة تحول كل صف إلى قاموس، وهو نفس الشكل الذي نعتمد عليه داخل القائمة.

هناك نقطة مهمة في السطر الذي يحول amount إلى float: ملفات CSV تخزن القيم كنصوص، بينما عمليات الجمع والمقارنة تحتاج رقمًا. لذلك نعيد المبلغ إلى نوع رقمي مباشرة بعد القراءة.

ماذا يحدث في أول تشغيل؟

إذا لم يجد البرنامج ملف expenses.csv فلن يتوقف. ستكون قائمة المصروفات فارغة، ويمكن للمستخدم إضافة أول مصروف. عند الحفظ ستنشأ نسخة الملف تلقائيًا، وفي التشغيل التالي ستتم قراءتها واستعادة السجلات.

إنشاء دالة حفظ المصروفات

بعد أي إضافة جديدة، يجب أن نحفظ البيانات مباشرة.

def save_expenses():
    with open(
        FILE_NAME,
        mode="w",
        newline="",
        encoding="utf-8"
    ) as file:
        fieldnames = [
            "amount",
            "category",
            "description",
            "date"
        ]

        writer = csv.DictWriter(
            file,
            fieldnames=fieldnames
        )

        writer.writeheader()

        for expense in expenses:
            writer.writerow(expense)

تفتح الدالة الملف بوضع w، أي أنها تكتب نسخة جديدة من محتوى القائمة في كل مرة. ثم نحدد أسماء الأعمدة بالترتيب نفسه الذي نستخدمه في كل سجل.

استدعاء writeheader() يكتب الصف الأول الذي يحتوي أسماء الحقول، وبعده تمر الحلقة على كل مصروف وتكتبه كسطر مستقل باستخدام writerow().

في هذا المشروع نستدعي save_expenses() مباشرة بعد إضافة المصروف. هذه الخطوة تجعل الحفظ جزءًا من العملية نفسها بدل الاعتماد على المستخدم ليتذكر أمر الحفظ لاحقًا.

لماذا نحول المبلغ إلى float؟

القيم المقروءة من CSV تكون نصوصًا، ولذلك إذا أردنا جمع المصروفات أو مقارنة الأرقام، يجب تحويل amount إلى float.

مثال غير صحيح:

total = "10" + "20"

الناتج هنا سيكون دمج نصوص، وليس جمعًا رياضيًا. فالقيمة "10" نص وليست العدد 10، وبالتالي سيصبح الناتج النص "1020" بدل الرقم 30.

لهذا السبب يجب أن يكون نوع البيانات مناسبًا للعملية التي نريد تنفيذها. النصوص مناسبة للعرض والوصف، أما المبالغ المالية في هذا المثال فنحتاجها كأرقام حتى نستطيع جمعها ومقارنتها بالميزانية.

الصحيح:

total = float("10") + float("20")

إنشاء دالة إضافة مصروف جديد

هذه من أهم الدوال في المشروع، ويجب أن تكون واضحة وآمنة.

def add_expense():
    try:
        amount = float(
            input("أدخل مبلغ المصروف: ")
        )
    except ValueError:
        print("خطأ: يجب إدخال رقم صحيح أو عشري.")
        return

    if amount <= 0:
        print("خطأ: يجب أن يكون المبلغ أكبر من صفر.")
        return

    category = input(
        "أدخل التصنيف (طعام / مواصلات / فواتير / ترفيه / أخرى): "
    ).strip()

    description = input(
        "أدخل وصفًا للمصروف: "
    ).strip()

    date = input(
        "أدخل التاريخ (YYYY-MM-DD): "
    ).strip()

    if category == "" or description == "" or date == "":
        print("خطأ: لا تترك الحقول النصية فارغة.")
        return

    expense = {
        "amount": amount,
        "category": category,
        "description": description,
        "date": date
    }

    expenses.append(expense)
    save_expenses()
    print("تمت إضافة المصروف بنجاح.")

شرح دالة add_expense خطوة بخطوة

تبدأ الدالة بالمبلغ لأنه أكثر حقل قد يسبب خطأ. فـ input() يعيد نصًا دائمًا، ولذلك نحاول تحويل المدخل إلى float. إذا كتب المستخدم كلمة بدل رقم، يلتقط except ValueError المشكلة ويعرض رسالة واضحة بدل توقف البرنامج.

بعد نجاح التحويل، نتحقق أن المبلغ أكبر من صفر. ثم نقرأ التصنيف والوصف والتاريخ ونستخدم strip() لحذف المسافات الزائدة من البداية والنهاية.

بعد التحقق، نجمع البيانات داخل قاموس واحد. هذه لحظة مهمة في تصميم البرنامج: بدل تمرير أربع قيم منفصلة إلى كل جزء لاحق، أصبح لدينا سجل واحد يمثل المصروف بكامل بياناته.

وأخيرًا نضيف السجل إلى expenses ثم نحفظ القائمة. بهذه الطريقة يصبح المصروف متاحًا للتحليل داخل الجلسة الحالية ومحفوظًا أيضًا للتشغيلات القادمة.

مثال على مصروف صحيح

إذا أدخل المستخدم مبلغًا 45.75 وتصنيفًا «مواصلات» ووصفًا «وقود» وتاريخًا صحيحًا، سيُضاف سجل جديد إلى القائمة ويُكتب صف جديد في ملف CSV. أما إذا كتب مبلغًا مثل abc فلن يصل البرنامج إلى مرحلة الحفظ أصلًا.

إضافة مصروف والتحقق من البيانات وربطه بالميزانية في بايثون

إنشاء دالة عرض جميع المصروفات

def show_expenses():
    if not expenses:
        print("لا توجد مصروفات مسجلة حتى الآن.")
        return

    print("\nجميع المصروفات:")

    for index, expense in enumerate(expenses, start=1):
        print(
            f"{index}. {expense['date']} | {expense['category']} | {expense['description']} | {expense['amount']}"
        )

استخدمنا enumerate() حتى يظهر رقم أمام كل مصروف بدل عرض القاموس الخام. هذا يجعل النتيجة أوضح للمستخدم، كما يمهد لإضافة ميزة الحذف أو التعديل لاحقًا باستخدام رقم السجل.

الدالة لا تغير البيانات ولا تحفظ شيئًا؛ وظيفتها القراءة والعرض فقط. الفصل بين دوال العرض ودوال التعديل يجعل البرنامج أسهل في الفهم والصيانة.

إنشاء دالة حساب إجمالي المصروفات

def get_total_expenses():
    total = 0

    for expense in expenses:
        total += expense["amount"]

    return total

في النسخة الأولى بدأنا المتغير total من صفر، ثم أضفنا مبلغ كل مصروف أثناء المرور على القائمة. هذه الطريقة ممتازة للمبتدئ لأنها تكشف منطق الجمع خطوة بخطوة.

ويمكن اختصارها أيضًا:

def get_total_expenses():
    return sum(
        expense["amount"]
        for expense in expenses
    )

عرض إجمالي المصروفات

def show_total_expenses():
    total = get_total_expenses()
    print(f"إجمالي المصروفات: {total}")

تلخيص المصروفات حسب التصنيف

هذه الدالة تجعل المشروع أكثر قوة، لأنها تعطي ملخصًا عمليًا مفيدًا.

def show_expenses_by_category():
    if not expenses:
        print("لا توجد بيانات لعرضها.")
        return

    category_totals = {}

    for expense in expenses:
        category = expense["category"]
        amount = expense["amount"]

        if category in category_totals:
            category_totals[category] += amount
        else:
            category_totals[category] = amount

    print("\nالمصروفات حسب التصنيف:")

    for category, total in category_totals.items():
        print(f"{category}: {total}")

مثال عملي على التجميع حسب التصنيف

افترض أن لدينا ثلاثة مصروفات: 20 للطعام، و15 للمواصلات، و30 أخرى للطعام. أثناء المرور على القائمة ستنشئ الدالة قاموسًا جديدًا للملخص. في النهاية تصبح قيمة «طعام» 50 وقيمة «مواصلات» 15.

هذه هي الفكرة الأساسية وراء كثير من عمليات تحليل البيانات: نجمع السجلات التي تشترك في تصنيف واحد، ثم نحسب قيمة إجمالية لكل مجموعة. لذلك هذا الجزء من المشروع لا يعلمك البرمجة فقط، بل يقدم لك بداية لفهم منطق تحليل البيانات.

كيف نحدد أعلى فئة إنفاق؟

بعد جمع المصروفات حسب التصنيف، نستطيع بسهولة معرفة الفئة الأعلى.

def show_top_category():
    if not expenses:
        print("لا توجد بيانات كافية.")
        return

    category_totals = {}

    for expense in expenses:
        category = expense["category"]
        amount = expense["amount"]

        category_totals[category] = category_totals.get(category, 0) + amount

    top_category = max(
        category_totals,
        key=category_totals.get
    )

    print(
        f"أعلى فئة إنفاق: {top_category} بقيمة {category_totals[top_category]}"
    )

بعد تكوين قاموس الإجماليات نستخدم max() مع key=category_totals.get. الفكرة أن الدالة لا تبحث عن أكبر اسم تصنيف، بل عن المفتاح الذي ترتبط به أكبر قيمة إنفاق.

إذا كانت النتائج مثلًا: طعام = 120، مواصلات = 70، فواتير = 250، فستكون «فواتير» هي أعلى فئة إنفاق. هذه المعلومة مفيدة لأنها تجيب عن سؤال عملي لا يقدمه مجموع المصروفات وحده: أين يذهب أكبر جزء من المال؟

تحديد الميزانية الشهرية

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

def set_monthly_budget():
    global monthly_budget

    try:
        budget = float(
            input("أدخل الميزانية الشهرية: ")
        )
    except ValueError:
        print("خطأ: يجب إدخال رقم صحيح.")
        return

    if budget <= 0:
        print("خطأ: الميزانية يجب أن تكون أكبر من صفر.")
        return

    monthly_budget = budget
    print(f"تم حفظ الميزانية الشهرية: {monthly_budget}")

حساب المبلغ المتبقي من الميزانية

def show_budget_status():
    if monthly_budget == 0:
        print("لم يتم تحديد ميزانية شهرية بعد.")
        return

    total = get_total_expenses()
    remaining = monthly_budget - total

    print(f"الميزانية الشهرية: {monthly_budget}")
    print(f"إجمالي المصروفات: {total}")
    print(f"المبلغ المتبقي: {remaining}")

    if remaining < 0:
        print("تحذير: لقد تجاوزت الميزانية الشهرية!")

كيف نحسب حالة الميزانية؟

الحساب بسيط: نطرح إجمالي المصروفات من الميزانية الشهرية. إذا كانت الميزانية 500 وإجمالي المصروفات 320، فالمتبقي هو 180. وإذا وصل الإجمالي إلى 560، فستصبح النتيجة -60، وهنا نعرف أن المستخدم تجاوز ميزانيته بمقدار 60.

القيمة السالبة ليست خطأ برمجيًا؛ هي معلومة مالية نستخدمها لإظهار التحذير. وهذا مثال جيد على الفرق بين التحقق من صحة البيانات وبين تفسير نتيجة حسابية داخل البرنامج.

في نسخة أكثر تطورًا يمكن أيضًا حساب نسبة استهلاك الميزانية أو إظهار تنبيه قبل الوصول إلى الحد، مثلًا عند تجاوز 80% من الميزانية.

تلخيص المصروفات حسب التصنيف وحساب الميزانية المتبقية في بايثون

إنشاء القائمة الرئيسية للبرنامج

حتى يكون المشروع سهل الاستخدام، سنعرض قائمة خيارات واضحة.

def show_menu():
    print("\n=== برنامج تتبع المصروفات ===")
    print("1. إضافة مصروف")
    print("2. عرض جميع المصروفات")
    print("3. عرض إجمالي المصروفات")
    print("4. عرض المصروفات حسب التصنيف")
    print("5. تحديد الميزانية الشهرية")
    print("6. عرض حالة الميزانية")
    print("7. عرض أعلى فئة إنفاق")
    print("8. خروج")

القائمة الرئيسية هي واجهة المستخدم في نسخة Terminal. كل رقم يقابل وظيفة واحدة، وهذا يجعل البرنامج سهل التجربة حتى لو لم يكن المستخدم يعرف الكود الموجود خلف كل خيار.

يمكنك لاحقًا تغيير النصوص أو إضافة خيارات جديدة دون تعديل الدوال الأساسية. هذه إحدى فوائد فصل واجهة الاستخدام عن منطق كل عملية.

البرنامج الرئيسي

def main():
    load_expenses()

    while True:
        show_menu()
        choice = input("اختر رقمًا من القائمة: ")

        if choice == "1":
            add_expense()
        elif choice == "2":
            show_expenses()
        elif choice == "3":
            show_total_expenses()
        elif choice == "4":
            show_expenses_by_category()
        elif choice == "5":
            set_monthly_budget()
        elif choice == "6":
            show_budget_status()
        elif choice == "7":
            show_top_category()
        elif choice == "8":
            print("تم إغلاق البرنامج.")
            break
        else:
            print("خيار غير صحيح، حاول مرة أخرى.")

الحلقة while True تبقي البرنامج مفتوحًا بعد تنفيذ كل عملية. المستخدم يضيف مصروفًا ثم يعود للقائمة بدل أن يضطر إلى تشغيل الملف مرة أخرى.

كل فرع داخل if / elif يربط اختيار المستخدم بدالة محددة. وعندما يختار الرقم 8 ننفذ break للخروج من الحلقة وإنهاء البرنامج بصورة واضحة.

تشغيل البرنامج

if __name__ == "__main__":
    main()

كيف تجمع أجزاء المشروع في ملف واحد؟

جميع أجزاء البرنامج التي كتبناها في الأقسام السابقة توضع داخل الملف expense_tracker.py بالترتيب نفسه: الاستيراد والمتغيرات أولًا، ثم دوال التحميل والحفظ، ثم دوال الإضافة والعرض والتحليل، وبعدها القائمة الرئيسية ودالة main()، وأخيرًا شرط التشغيل.

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

تجربة عملية: ماذا يحدث عند تشغيل البرنامج؟

بعد حفظ الملف وتشغيله ستظهر القائمة الرئيسية. لنفترض أن المستخدم اختار إضافة مصروف وسجّل 30 للطعام، ثم سجّل 15 للمواصلات. عند طلب الإجمالي سيعيد البرنامج 45، وعند عرض المصروفات حسب التصنيف سيعرض كل فئة ومجموعها.

=== برنامج تتبع المصروفات ===
1. إضافة مصروف
2. عرض جميع المصروفات
3. عرض إجمالي المصروفات
4. عرض المصروفات حسب التصنيف
5. تحديد الميزانية الشهرية
6. عرض حالة الميزانية
7. عرض أعلى فئة إنفاق
8. خروج

إجمالي المصروفات: 45.0

إذا أغلق المستخدم البرنامج ثم شغله مرة أخرى، تستدعي load_expenses() ملف CSV وتعيد السجلات السابقة إلى القائمة. وهذا هو الفرق بين متغيرات تعيش أثناء التشغيل فقط وبيانات محفوظة تستطيع استعادتها لاحقًا.

كيف تختبر البرنامج وتتأكد أنه يعمل؟

لا تكتفِ بتجربة حالة واحدة. اختبر السيناريوهات التي قد يستخدمها شخص حقيقي، لأن الأخطاء تظهر غالبًا في المدخلات غير المثالية.

  • أضف مصروفًا صحيحًا وتأكد أنه يظهر في القائمة.
  • أغلق البرنامج وافتحه وتأكد أن المصروف لم يختفِ.
  • اكتب نصًا في خانة المبلغ وتأكد أن البرنامج يعرض رسالة خطأ بدل التوقف.
  • جرّب قيمة صفر أو قيمة سالبة.
  • أضف أكثر من مصروف للتصنيف نفسه وتأكد أن الإجمالي صحيح.
  • حدد ميزانية أقل من إجمالي المصروفات وتأكد من ظهور تحذير التجاوز.

بهذه الاختبارات تكون قد تحققت من المسارات الأساسية للبرنامج، وليس فقط من أن الملف يعمل دون SyntaxError.

ماذا سيستفيد المبتدئ من هذا المشروع؟

هذا المشروع ليس مجرد مثال بسيط، بل يجمع بين عدة مهارات مهمة في بايثون:

  • التعامل مع القوائم والقواميس.
  • تقسيم البرنامج إلى دوال واضحة.
  • التعامل مع الإدخال من المستخدم.
  • التحقق من البيانات ومعالجة الأخطاء.
  • الحفظ والقراءة من الملفات.
  • تنظيم البيانات وتحليلها.

متى نستخدم CSV ومتى نحتاج SQLite؟

ملف CSV ممتاز للتعلم وللبيانات الصغيرة التي لا تحتاج إلى علاقات معقدة. لكنه ليس قاعدة بيانات كاملة؛ فإذا أردت البحث السريع في آلاف السجلات، أو تعديل سجل محدد بكثرة، أو دعم أكثر من مستخدم، فستحتاج غالبًا إلى حل أقوى.

قاعدة SQLite مناسبة كخطوة تالية لهذا المشروع لأنها تعمل من ملف واحد أيضًا، لكنها تسمح بالبحث والتصفية والتعديل باستخدام أوامر SQL. الانتقال إليها بعد إتقان نسخة CSV سيكون تطويرًا طبيعيًا للمشروع.

كيف تطور المشروع لاحقًا؟

إذا أردت تحويل هذا المشروع إلى نسخة أقوى، فهذه بعض الأفكار الممتازة:

  • إضافة إمكانية حذف مصروف.
  • إضافة تعديل مصروف موجود.
  • عرض المصروفات حسب تاريخ معين أو شهر محدد.
  • تحويل المشروع من Terminal إلى واجهة رسومية.
  • إضافة رسوم بيانية للإنفاق.
  • تصدير ملخص شهري.
  • حفظ الميزانية في ملف منفصل حتى تبقى بعد إغلاق البرنامج.

لماذا هذا المشروع أفضل من حفظ أكواد منفصلة؟

تعلم الأوامر منفردة مهم في البداية، لكن بناء مشروع يجبرك على ربطها ببعضها. هنا استخدمنا input() للحصول على البيانات، وdict لتنظيم سجل المصروف، وlist لتجميع السجلات، والدوال لتقسيم المسؤوليات، وCSV للحفظ الدائم.

هذه الروابط بين المفاهيم هي ما تحتاجه عند الانتقال من مرحلة «أعرف الأمر» إلى مرحلة «أستطيع بناء برنامج». لذلك حاول بعد إنهاء الدرس حذف جزء صغير وإعادة كتابته بنفسك بدل نسخ الملف كاملًا دون تجربة.

أخطاء شائعة في هذا المشروع

1. نسيان حفظ البيانات بعد الإضافة

إذا أضفت مصروفًا إلى القائمة ولم تنفذ save_expenses()، فستضيع البيانات عند إغلاق البرنامج.

2. عدم تحويل المبلغ إلى رقم

إذا بقي المبلغ كنص، فلن تعمل عمليات الجمع والمقارنة بشكل صحيح.

3. قراءة CSV دون معالجة الأخطاء

قد يحتوي الملف على بيانات غير متوقعة أو تنسيق خاطئ، ولذلك من الأفضل دائمًا أن تكتب كودًا يتحمل الأخطاء قدر الإمكان.

4. قبول قيمة سالبة كمصروف

في هذا المشروع يجب منع المبالغ السالبة أو الصفرية لأنها لا تمثل مصروفًا منطقيًا.

أخطاء شائعة عند بناء برنامج تتبع المصروفات ببايثون

نسخة محسنة: إضافة try/except أثناء التحميل

يمكن تحسين دالة القراءة لتصبح أكثر أمانًا:

def load_expenses():
    global expenses

    if not os.path.exists(FILE_NAME):
        expenses = []
        return

    try:
        with open(
            FILE_NAME,
            mode="r",
            newline="",
            encoding="utf-8"
        ) as file:
            reader = csv.DictReader(file)
            expenses = []

            for row in reader:
                row["amount"] = float(row["amount"])
                expenses.append(row)

    except Exception as error:
        print(f"حدث خطأ أثناء قراءة الملف: {error}")
        expenses = []

تمرين عملي للمبتدئ

جرّب بنفسك تطوير المشروع بإضافة خيار جديد:

  • عرض المصروفات الخاصة بتصنيف معين فقط.
  • مثلاً إذا كتب المستخدم «طعام»، تعرض له كل مصروفات الطعام فقط.

فكرة الحل

def show_expenses_for_category():
    category_name = input("أدخل اسم التصنيف: ").strip()

    found = False

    for expense in expenses:
        if expense["category"] == category_name:
            print(expense)
            found = True

    if not found:
        print("لا توجد مصروفات لهذا التصنيف.")

روابط داخلية مفيدة من بايثون العرب

مصادر خارجية رسمية للتوسع

الخلاصة

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

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

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

{alertSuccess} القاعدة المهمة: لا تجعل المشروع مجرد كود يضيف بيانات فقط. اجعل البيانات تُحفظ، وتُحلل، وتنتج معلومات مفيدة مثل إجمالي الإنفاق والمتبقي من الميزانية، فهنا يبدأ المشروع بالتحول من مثال تعليمي إلى برنامج عملي حقيقي.

أسئلة شائعة

ما أفضل مشروع بايثون للمبتدئين بعد الأساسيات؟

من أفضل المشاريع العملية للمبتدئين مشروع تتبع المصروفات، لأنه يجمع بين الدوال والقوائم والقواميس والملفات والتحقق من الإدخال في مثال واحد واضح ومفيد.

لماذا استخدمنا CSV في هذا المشروع؟

استخدمنا CSV لأنه بسيط وسهل القراءة والكتابة، ويمكن فتحه أيضًا في برامج الجداول مثل Excel، وهو مناسب جدًا للمشاريع التعليمية والصغيرة.

كيف أحسب إجمالي المصروفات في بايثون؟

يمكنك جمع قيم amount من جميع عناصر القائمة باستخدام حلقة for أو باستخدام sum().

كيف أعرف أنني تجاوزت الميزانية؟

احسب إجمالي المصروفات ثم اطرحها من الميزانية الشهرية. إذا كانت النتيجة سالبة فهذا يعني أنك تجاوزت الميزانية.

هل يمكن تطوير البرنامج لاحقًا؟

نعم، ويمكنك إضافة الحذف والتعديل والتصفية حسب التاريخ أو حتى بناء واجهة رسومية أو لوحة تحليل للإنفاق.

هل هذا المشروع مناسب للنشر في معرض أعمالي؟

نعم، لأنه مشروع عملي وواضح ويظهر أنك تستطيع التعامل مع البيانات والملفات وبناء منطق برمجي مفيد.

إرسال تعليق

أحدث أقدم