ساخت برنامه مدیریت نمرات دانشآموزان با پایتون؛ پروژه کامل با JSON، میانگین و رتبهبندی

🗓 آخرین بهروزرسانی:
ساخت برنامه مدیریت نمرات دانشآموزان با پایتون یکی از پروژههای مناسب برای تمرین همزمان لیست، دیکشنری، تابع، حلقه، شرط، فایل JSON و اعتبارسنجی ورودی است. در این آموزش یک برنامه واقعیتر از تمرینهای ساده میسازیم؛ برنامهای که بتواند دانشآموز جدید ثبت کند، چند نمره برای هر دانشآموز نگه دارد، میانگین را محاسبه کند، وضعیت قبولی را نمایش دهد، دانشآموزان را بر اساس میانگین رتبهبندی کند و تمام اطلاعات را در فایل JSON ذخیره کند تا با بستن برنامه از بین نروند.
این مقاله دوازدهمین پروژه فاز اول دسته تمرین و پروژههای پایتون است. اگر پروژه ساخت Todo List با پایتون را انجام داده باشید، با ایده ذخیره اطلاعات در JSON آشنا هستید. در این پروژه همان مفهوم را با دادههای ساختاریافتهتر و محاسبات واقعیتر ادامه میدهیم.
در این پروژه چه چیزی میسازیم؟
- ثبت دانشآموز جدید با شناسه یکتا
- افزودن چند نمره برای هر دانشآموز
- نمایش مشخصات و نمرات هر دانشآموز
- محاسبه میانگین نمرات
- نمایش وضعیت قبولی یا نیاز به تلاش بیشتر
- جستوجوی دانشآموز با شناسه
- حذف دانشآموز
- رتبهبندی بر اساس میانگین
- نمایش بهترین و ضعیفترین میانگین
- اعتبارسنجی نمره در بازه صفر تا ۲۰
- ذخیره دائمی اطلاعات در JSON
- بارگذاری خودکار دادهها هنگام اجرای برنامه
- مدیریت فایل JSON خراب یا نامعتبر
ساختار داده مناسب برای مدیریت نمرات
برای این پروژه از یک دیکشنری اصلی استفاده میکنیم که کلید آن شناسه دانشآموز و مقدار آن اطلاعات مربوط به همان دانشآموز است:
{
"1001": {
"name": "علی رضایی",
"grades": [18.5, 17, 20]
},
"1002": {
"name": "سارا احمدی",
"grades": [16, 19.25, 18]
}
}این مدل چند مزیت دارد. شناسه دانشآموز بهصورت طبیعی یکتا است، دسترسی به هر فرد سریع و خواناست و لیست نمرات نیز امکان افزودن نمرههای جدید را فراهم میکند. اگر با این ساختار آشنا نیستید، مقاله دیکشنری در پایتون و لیستها در پایتون را مرور کنید.
مرحله اول: ساخت فایل پروژه
یک فایل جدید با نام زیر بسازید:
student_grade_manager.pyبرنامه هنگام اجرا در همان پوشه فایل زیر را ایجاد میکند:
students.jsonمرحله دوم: وارد کردن ماژولهای مورد نیاز
import json
from pathlib import Pathماژول json برای خواندن و نوشتن دادهها و Path برای مدیریت مسیر فایل استفاده میشود.
مرحله سوم: تعریف تنظیمات اصلی
DATA_FILE = Path("students.json")
PASS_GRADE = 10.0
MIN_GRADE = 0.0
MAX_GRADE = 20.0بهجای پخش کردن عددهای ثابت در بخشهای مختلف برنامه، آنها را در ابتدای فایل تعریف میکنیم. اگر بعداً بخواهیم حد قبولی یا بازه نمره را تغییر دهیم، فقط یک بخش ویرایش میشود.
مرحله چهارم: بارگذاری اطلاعات از JSON
def load_students():
if not DATA_FILE.exists():
return {}
try:
with DATA_FILE.open(
"r",
encoding="utf-8",
) as file:
data = json.load(file)
except (
json.JSONDecodeError,
OSError,
):
print(
"فایل اطلاعات قابل خواندن نیست. "
"برنامه با داده خالی شروع شد."
)
return {}
if not isinstance(data, dict):
return {}
return dataاگر فایل هنوز ساخته نشده باشد، دیکشنری خالی برمیگردد. اگر فایل JSON خراب باشد نیز برنامه متوقف نمیشود. برای جزئیات بیشتر درباره این بخش، آموزش کار با فایلهای JSON در پایتون و مدیریت خطا در پایتون مفید هستند.
مرحله پنجم: ذخیره اطلاعات
def save_students(students):
with DATA_FILE.open(
"w",
encoding="utf-8",
) as file:
json.dump(
students,
file,
ensure_ascii=False,
indent=2,
)گزینه ensure_ascii=False باعث میشود نامهای فارسی در فایل بهصورت خوانا ذخیره شوند.
مرحله ششم: ثبت دانشآموز جدید
شناسه باید خالی نباشد و قبلاً ثبت نشده باشد. نام دانشآموز نیز نباید خالی باشد.
def add_student(students):
student_id = input(
"شناسه دانشآموز: "
).strip()
if not student_id:
print("شناسه نمیتواند خالی باشد.")
return
if student_id in students:
print(
"دانشآموزی با این شناسه قبلاً ثبت شده است."
)
return
name = input(
"نام و نام خانوادگی: "
).strip()
if not name:
print("نام نمیتواند خالی باشد.")
return
students[student_id] = {
"name": name,
"grades": [],
}
save_students(students)
print("دانشآموز با موفقیت ثبت شد.")مرحله هفتم: دریافت نمره معتبر
نمره ممکن است اعشاری باشد، بنابراین از float استفاده میکنیم. علاوه بر عدد بودن، مقدار باید بین صفر تا ۲۰ باشد.
def get_grade():
value = input(
"نمره را وارد کنید: "
).strip()
try:
grade = float(value)
except ValueError:
print("نمره باید عدد باشد.")
return None
if not (
MIN_GRADE
<= grade
<= MAX_GRADE
):
print(
"نمره باید بین 0 تا 20 باشد."
)
return None
return gradeمرحله هشتم: افزودن نمره
def add_grade(students):
student_id = input(
"شناسه دانشآموز: "
).strip()
student = students.get(student_id)
if student is None:
print(
"دانشآموزی با این شناسه پیدا نشد."
)
return
grade = get_grade()
if grade is None:
return
student["grades"].append(grade)
save_students(students)
print("نمره ثبت شد.")متد get() اجازه میدهد در صورت نبود شناسه، بهجای خطای KeyError مقدار None دریافت کنیم.
مرحله نهم: محاسبه میانگین
def calculate_average(grades):
if not grades:
return None
return sum(grades) / len(grades)دانشآموزی که هنوز نمره ندارد نباید میانگین صفر دریافت کند، چون صفر با «بدون نمره» تفاوت دارد. به همین دلیل در این حالت None برمیگردانیم.
مرحله دهم: تعیین وضعیت قبولی
def get_status(average):
if average is None:
return "بدون نمره"
if average >= PASS_GRADE:
return "قبول"
return "نیاز به تلاش بیشتر"در این پروژه حد قبولی ۱۰ تعریف شده است. این مقدار فقط بخشی از منطق نمونه پروژه است و میتوانید آن را متناسب با سیستم آموزشی موردنظر تغییر دهید.
مرحله یازدهم: نمایش یک دانشآموز
def show_student(
student_id,
student,
):
grades = student.get(
"grades",
[],
)
average = calculate_average(
grades
)
print("\n----------------")
print(f"شناسه: {student_id}")
print(
f"نام: {student.get('name', '')}"
)
if grades:
formatted = ", ".join(
f"{grade:.2f}"
for grade in grades
)
print(f"نمرات: {formatted}")
print(
f"میانگین: {average:.2f}"
)
else:
print("نمرات: ثبت نشده")
print(
f"وضعیت: {get_status(average)}"
)مرحله دوازدهم: نمایش همه دانشآموزان
def show_all_students(students):
if not students:
print(
"هنوز دانشآموزی ثبت نشده است."
)
return
for student_id, student in (
students.items()
):
show_student(
student_id,
student,
)مرحله سیزدهم: جستوجوی دانشآموز
def search_student(students):
student_id = input(
"شناسه مورد جستوجو: "
).strip()
student = students.get(student_id)
if student is None:
print(
"دانشآموز پیدا نشد."
)
return
show_student(
student_id,
student,
)مرحله چهاردهم: حذف دانشآموز
def delete_student(students):
student_id = input(
"شناسه دانشآموز برای حذف: "
).strip()
student = students.get(student_id)
if student is None:
print(
"دانشآموز پیدا نشد."
)
return
confirm = input(
f"حذف {student['name']}؟ (بله/خیر): "
).strip().lower()
if confirm not in {
"بله",
"yes",
"y",
}:
print("حذف لغو شد.")
return
del students[student_id]
save_students(students)
print("دانشآموز حذف شد.")مرحله پانزدهم: رتبهبندی دانشآموزان
برای رتبهبندی، دانشآموزانی را که حداقل یک نمره دارند جدا میکنیم و سپس با sorted() بر اساس میانگین از بیشترین به کمترین مرتب میکنیم.
def ranking(students):
rows = []
for student_id, student in (
students.items()
):
grades = student.get(
"grades",
[],
)
average = calculate_average(
grades
)
if average is None:
continue
rows.append({
"id": student_id,
"name": student.get(
"name",
"",
),
"average": average,
})
rows.sort(
key=lambda item: item["average"],
reverse=True,
)
if not rows:
print(
"برای رتبهبندی هنوز نمرهای وجود ندارد."
)
return
print("\n=== رتبهبندی ===")
for rank, item in enumerate(
rows,
start=1,
):
print(
f"{rank}. {item['name']} | "
f"{item['average']:.2f}"
)در این بخش از تابع Lambda برای کلید مرتبسازی استفاده میکنیم. اگر میخواهید این مفهوم را جداگانه تمرین کنید، مقاله توابع Lambda در پایتون را بخوانید.
مرحله شانزدهم: ساخت گزارش کلی کلاس
میتوانیم از همان دادهها برای ساخت یک گزارش ساده استفاده کنیم.
def class_report(students):
averages = []
for student in students.values():
average = calculate_average(
student.get("grades", [])
)
if average is not None:
averages.append(average)
if not averages:
print(
"برای تهیه گزارش نمرهای وجود ندارد."
)
return
class_average = (
sum(averages) / len(averages)
)
print("\n=== گزارش کلاس ===")
print(
f"تعداد دانشآموزان دارای نمره: "
f"{len(averages)}"
)
print(
f"میانگین کلاس: {class_average:.2f}"
)
print(
f"بالاترین میانگین: {max(averages):.2f}"
)
print(
f"پایینترین میانگین: {min(averages):.2f}"
)مرحله هفدهم: ساخت منوی برنامه
def show_menu():
print("\n=== مدیریت نمرات ===")
print("1) ثبت دانشآموز")
print("2) افزودن نمره")
print("3) نمایش همه")
print("4) جستوجو")
print("5) رتبهبندی")
print("6) گزارش کلاس")
print("7) حذف دانشآموز")
print("0) خروج")کد کامل برنامه مدیریت نمرات دانشآموزان با پایتون
import json
from pathlib import Path
DATA_FILE = Path("students.json")
PASS_GRADE = 10.0
MIN_GRADE = 0.0
MAX_GRADE = 20.0
def load_students():
if not DATA_FILE.exists():
return {}
try:
with DATA_FILE.open(
"r",
encoding="utf-8",
) as file:
data = json.load(file)
except (
json.JSONDecodeError,
OSError,
):
print(
"فایل اطلاعات قابل خواندن نیست."
)
return {}
return data if isinstance(data, dict) else {}
def save_students(students):
with DATA_FILE.open(
"w",
encoding="utf-8",
) as file:
json.dump(
students,
file,
ensure_ascii=False,
indent=2,
)
def calculate_average(grades):
if not grades:
return None
return sum(grades) / len(grades)
def get_status(average):
if average is None:
return "بدون نمره"
if average >= PASS_GRADE:
return "قبول"
return "نیاز به تلاش بیشتر"
def add_student(students):
student_id = input(
"شناسه دانشآموز: "
).strip()
if not student_id:
print("شناسه نمیتواند خالی باشد.")
return
if student_id in students:
print(
"این شناسه قبلاً ثبت شده است."
)
return
name = input(
"نام و نام خانوادگی: "
).strip()
if not name:
print("نام نمیتواند خالی باشد.")
return
students[student_id] = {
"name": name,
"grades": [],
}
save_students(students)
print("دانشآموز ثبت شد.")
def get_grade():
value = input(
"نمره را وارد کنید: "
).strip()
try:
grade = float(value)
except ValueError:
print("نمره باید عدد باشد.")
return None
if not (
MIN_GRADE
<= grade
<= MAX_GRADE
):
print(
"نمره باید بین 0 تا 20 باشد."
)
return None
return grade
def add_grade(students):
student_id = input(
"شناسه دانشآموز: "
).strip()
student = students.get(student_id)
if student is None:
print("دانشآموز پیدا نشد.")
return
grade = get_grade()
if grade is None:
return
student["grades"].append(grade)
save_students(students)
print("نمره ثبت شد.")
def show_student(
student_id,
student,
):
grades = student.get("grades", [])
average = calculate_average(grades)
print("\n----------------")
print(f"شناسه: {student_id}")
print(
f"نام: {student.get('name', '')}"
)
if grades:
formatted = ", ".join(
f"{grade:.2f}"
for grade in grades
)
print(f"نمرات: {formatted}")
print(
f"میانگین: {average:.2f}"
)
else:
print("نمرات: ثبت نشده")
print(
f"وضعیت: {get_status(average)}"
)
def show_all_students(students):
if not students:
print(
"هنوز دانشآموزی ثبت نشده است."
)
return
for student_id, student in (
students.items()
):
show_student(
student_id,
student,
)
def search_student(students):
student_id = input(
"شناسه مورد جستوجو: "
).strip()
student = students.get(student_id)
if student is None:
print("دانشآموز پیدا نشد.")
return
show_student(
student_id,
student,
)
def ranking(students):
rows = []
for student_id, student in (
students.items()
):
average = calculate_average(
student.get("grades", [])
)
if average is None:
continue
rows.append({
"id": student_id,
"name": student.get(
"name",
"",
),
"average": average,
})
rows.sort(
key=lambda item: item["average"],
reverse=True,
)
if not rows:
print(
"برای رتبهبندی نمرهای وجود ندارد."
)
return
print("\n=== رتبهبندی ===")
for rank, item in enumerate(
rows,
start=1,
):
print(
f"{rank}. {item['name']} | "
f"{item['average']:.2f}"
)
def class_report(students):
averages = []
for student in students.values():
average = calculate_average(
student.get("grades", [])
)
if average is not None:
averages.append(average)
if not averages:
print(
"برای گزارش نمرهای وجود ندارد."
)
return
class_average = (
sum(averages) / len(averages)
)
print("\n=== گزارش کلاس ===")
print(
f"تعداد: {len(averages)}"
)
print(
f"میانگین کلاس: {class_average:.2f}"
)
print(
f"بالاترین میانگین: "
f"{max(averages):.2f}"
)
print(
f"پایینترین میانگین: "
f"{min(averages):.2f}"
)
def delete_student(students):
student_id = input(
"شناسه دانشآموز برای حذف: "
).strip()
student = students.get(student_id)
if student is None:
print("دانشآموز پیدا نشد.")
return
confirm = input(
f"حذف {student['name']}؟ "
"(بله/خیر): "
).strip().lower()
if confirm not in {
"بله",
"yes",
"y",
}:
print("حذف لغو شد.")
return
del students[student_id]
save_students(students)
print("دانشآموز حذف شد.")
def show_menu():
print("\n=== مدیریت نمرات ===")
print("1) ثبت دانشآموز")
print("2) افزودن نمره")
print("3) نمایش همه")
print("4) جستوجو")
print("5) رتبهبندی")
print("6) گزارش کلاس")
print("7) حذف دانشآموز")
print("0) خروج")
def main():
students = load_students()
while True:
show_menu()
choice = input(
"انتخاب شما: "
).strip()
if choice == "1":
add_student(students)
elif choice == "2":
add_grade(students)
elif choice == "3":
show_all_students(students)
elif choice == "4":
search_student(students)
elif choice == "5":
ranking(students)
elif choice == "6":
class_report(students)
elif choice == "7":
delete_student(students)
elif choice == "0":
print("برنامه پایان یافت.")
break
else:
print("گزینه نامعتبر است.")
if __name__ == "__main__":
main()نمونه اجرای برنامه
=== مدیریت نمرات ===
1) ثبت دانشآموز
2) افزودن نمره
3) نمایش همه
4) جستوجو
5) رتبهبندی
6) گزارش کلاس
7) حذف دانشآموز
0) خروج
انتخاب شما: 1
شناسه دانشآموز: 1001
نام و نام خانوادگی: علی رضایی
دانشآموز ثبت شد.بعد از ثبت چند نمره، خروجی میتواند به این شکل باشد:
شناسه: 1001
نام: علی رضایی
نمرات: 18.50, 17.00, 20.00
میانگین: 18.50
وضعیت: قبولرتبهبندی چگونه کار میکند؟
تابع ranking() برای هر دانشآموز میانگین را محاسبه میکند، افراد بدون نمره را کنار میگذارد و سپس لیست را بهصورت نزولی مرتب میکند. برای مثال:
=== رتبهبندی ===
1. سارا احمدی | 19.08
2. علی رضایی | 18.50
3. مهدی کریمی | 15.75چرا از ID بهعنوان کلید دیکشنری استفاده کردیم؟
نام افراد ممکن است تکراری باشد، اما شناسه باید یکتا باشد. اگر نام را بهعنوان کلید قرار دهیم، ثبت دو دانشآموز با نام یکسان مشکلساز میشود. استفاده از ID این مشکل را حل میکند و جستوجو نیز سادهتر میشود.
اشتباههای رایج در پروژه مدیریت نمرات
ذخیره فقط یک نمره برای هر دانشآموز
اگر مقدار grade را بهصورت یک عدد نگه داریم، امکان ثبت چند آزمون وجود ندارد. استفاده از لیست نمرات انعطاف بیشتری ایجاد میکند.
محاسبه میانگین لیست خالی
تقسیم مجموع بر طول لیست خالی باعث تقسیم بر صفر میشود. قبل از محاسبه باید وجود حداقل یک نمره بررسی شود.
پذیرفتن نمره خارج از بازه
تبدیل موفق ورودی به عدد به معنای معتبر بودن آن نیست. مقدار ۳۷ یا منفی ۵ نیز عدد است، اما برای این سیستم پذیرفته نمیشود.
فراموش کردن ذخیره بعد از تغییر
بعد از افزودن دانشآموز، نمره یا حذف اطلاعات باید فایل JSON دوباره ذخیره شود، در غیر این صورت تغییرات با بسته شدن برنامه از بین میروند.
رتبهبندی دانشآموز بدون نمره
برای دانشآموزی که هنوز نمرهای ندارد میانگین معناداری وجود ندارد، بنابراین در رتبهبندی این افراد را کنار میگذاریم.
چطور پروژه را توسعه دهیم؟
- درسهای جداگانه: بهجای یک لیست نمره، برای ریاضی، فیزیک و برنامهنویسی لیست مستقل بسازید.
- ضریب درس: میانگین وزنی بر اساس ضریب هر درس محاسبه کنید.
- ویرایش نمره: امکان اصلاح نمره ثبتشده را اضافه کنید.
- حذف یک نمره: بهجای حذف کل دانشآموز، یک نمره مشخص را حذف کنید.
- جستوجو با نام: علاوه بر ID، امکان جستوجوی بخشی از نام را اضافه کنید.
- فیلتر قبولی: فقط دانشآموزان قبول یا مردود را نمایش دهید.
- گزارش آماری: تعداد افراد بالاتر از میانگین کلاس را محاسبه کنید.
- خروجی CSV: گزارش کلاس را برای استفاده در Excel به CSV تبدیل کنید.
- SQLite: در نسخه پیشرفتهتر JSON را با دیتابیس جایگزین کنید.
- رابط گرافیکی: با Tkinter فرم ثبت دانشآموز و جدول نمرات بسازید.
تمرین پیشنهادی: میانگین وزنی درسها
ساختار داده را به شکل زیر تغییر دهید:
{
"math": {
"grade": 18,
"coefficient": 3
},
"physics": {
"grade": 16,
"coefficient": 2
}
}سپس میانگین وزنی را با فرمول مجموع حاصلضرب نمره در ضریب تقسیم بر مجموع ضرایب محاسبه کنید. این تمرین پروژه را از یک مدیر ساده نمرات به یک سیستم کارنامه واقعیتر نزدیک میکند.
این پروژه چه مفاهیمی را تمرین میدهد؟
- Dictionary و List
- ساختار داده تو در تو
- JSON
- Pathlib
- خواندن و نوشتن فایل
- توابع و پارامترها
- حلقه for و while
- شرطها
- Exception Handling
- Lambda و sorted
- sum، min، max و len
- اعتبارسنجی ورودی
- محاسبه میانگین
- رتبهبندی دادهها
- ماندگاری اطلاعات
جمعبندی
در این آموزش یک برنامه کامل مدیریت نمرات دانشآموزان با پایتون ساختیم. اطلاعات هر دانشآموز با شناسه یکتا نگهداری میشود، چند نمره برای هر فرد قابل ثبت است، میانگین و وضعیت محاسبه میشود، رتبهبندی و گزارش کلی کلاس داریم و تمام دادهها در فایل JSON باقی میمانند.
مهمترین نکته این پروژه، ترکیب چند مفهوم پایه در یک ساختار منظم است. دیکشنری مسئول نگهداری اطلاعات دانشآموزان است، لیست نمرات را ذخیره میکند، توابع هر مسئولیت را جدا میکنند و JSON باعث ماندگاری دادهها میشود. بعد از اجرای نسخه پایه، افزودن درسهای جداگانه و میانگین وزنی بهترین تمرین برای توسعه پروژه است.
📌 همچنین بخوانید: تمرین پایتون با جواب، ساخت بازی با پایتون و Pygame و ساخت ربات با پایتون