تمرین و پروژه‌های پایتون

ساخت آزمون چهارگزینه‌ای با پایتون؛ پروژه کامل با امتیازدهی و نتیجه نهایی

ساخت آزمون چهارگزینه‌ای با پایتون

🗓 آخرین به‌روزرسانی:

ساخت آزمون چهارگزینه‌ای با پایتون یکی از پروژه‌های کاربردی برای تمرین کار با لیست، دیکشنری، حلقه، شرط، تابع و مدیریت ورودی کاربر است. در این آموزش فقط چند سؤال را پشت‌سرهم نمایش نمی‌دهیم؛ یک Quiz کامل می‌سازیم که سؤال‌ها و گزینه‌ها را از یک ساختار داده منظم می‌خواند، پاسخ کاربر را اعتبارسنجی می‌کند، امتیاز را محاسبه می‌کند، پاسخ درست را توضیح می‌دهد و در پایان درصد موفقیت و سطح نتیجه را نمایش می‌دهد.

این مقاله هفتمین پروژه از فاز اول دسته تمرین و پروژه‌های پایتون است. اگر پروژه‌های قبلی مثل بازی سنگ کاغذ قیچی با پایتون یا برنامه محاسبه BMI با پایتون را انجام داده باشید، ساختار منو، حلقه و اعتبارسنجی ورودی برایتان آشنا خواهد بود.

در این پروژه چه چیزی می‌سازیم؟

  • تعریف بانک سؤال با List و Dictionary
  • نمایش سؤال و چهار گزینه A، B، C و D
  • دریافت پاسخ معتبر از کاربر
  • امکان خروج از آزمون با Q
  • بررسی درست یا غلط بودن هر پاسخ
  • نمایش توضیح کوتاه بعد از هر سؤال
  • ثبت امتیاز کل
  • محاسبه درصد پاسخ صحیح
  • نمایش سطح نتیجه
  • مرور سؤال‌هایی که اشتباه پاسخ داده شده‌اند
  • امکان اجرای دوباره آزمون

پیش‌نیازهای پروژه

در این پروژه از هیچ کتابخانه خارجی استفاده نمی‌کنیم. آشنایی با تابع input در پایتون، شرط‌های if و elif، توابع و پارامترها و ساختارهای لیست و دیکشنری برای درک بهتر کد مفید است.

فایل پروژه را با نام زیر بسازید:

python_quiz.py

و اجرا کنید:

python python_quiz.py

مرحله اول: طراحی بانک سؤال

اولین تصمیم مهم این پروژه، نحوه نگهداری سؤال‌هاست. نوشتن سؤال‌ها با چند متغیر جداگانه خیلی سریع کد را شلوغ می‌کند. روش بهتر این است که هر سؤال یک دیکشنری باشد و تمام سؤال‌ها داخل یک لیست قرار بگیرند.

QUESTIONS = [
    {
        "question": "خروجی print(2 ** 3) چیست؟",
        "options": {
            "A": "6",
            "B": "8",
            "C": "9",
            "D": "10",
        },
        "answer": "B",
        "explanation": (
            "عملگر ** برای توان است؛ "
            "2 به توان 3 برابر 8 می‌شود."
        ),
    },
]

مزیت این ساختار این است که سؤال، گزینه‌ها، پاسخ صحیح و توضیح هر سؤال کنار هم نگه داشته می‌شوند. در نتیجه برای افزودن سؤال جدید فقط کافی است یک دیکشنری دیگر به لیست اضافه کنیم.

چرا از دیکشنری برای گزینه‌ها استفاده می‌کنیم؟

گزینه‌ها به شکل کلید و مقدار ذخیره شده‌اند:

"options": {
    "A": "6",
    "B": "8",
    "C": "9",
    "D": "10",
}

این روش باعث می‌شود هم نمایش گزینه‌ها ساده باشد و هم بررسی کنیم پاسخ کاربر داخل کلیدهای معتبر قرار دارد یا نه.

مرحله دوم: دریافت پاسخ معتبر از کاربر

کاربر ممکن است حروف کوچک، فاصله اضافی یا یک مقدار نامعتبر وارد کند. یک تابع جداگانه برای کنترل ورودی می‌سازیم:

def get_answer(options):
    valid = set(options)

    while True:
        answer = input(
            "پاسخ شما (A/B/C/D یا Q برای خروج): "
        ).strip().upper()

        if answer == "Q":
            return None

        if answer in valid:
            return answer

        print(
            "گزینه نامعتبر است. "
            "یکی از A، B، C یا D را وارد کنید."
        )

متد strip() فاصله‌های ابتدا و انتهای ورودی را حذف می‌کند و upper() پاسخ‌هایی مثل b را به B تبدیل می‌کند.

چرا set(options) استفاده شده است؟

وقتی روی یک دیکشنری پیمایش می‌کنیم، کلیدهای آن دریافت می‌شوند. بنابراین set(options) مجموعه‌ای مانند زیر می‌سازد:

{"A", "B", "C", "D"}

حالا بررسی عضویت پاسخ کاربر خوانا و مستقیم است.

مرحله سوم: نمایش یک سؤال

مسئولیت نمایش سؤال و بررسی پاسخ را در تابع ask_question() قرار می‌دهیم.

def ask_question(
    question_data,
    number,
    total,
):
    print(
        f"\n--- سؤال {number} از {total} ---"
    )

    print(
        question_data["question"]
    )

    for key, text in (
        question_data["options"].items()
    ):
        print(f"{key}) {text}")

    user_answer = get_answer(
        question_data["options"]
    )

    if user_answer is None:
        return None

    is_correct = (
        user_answer
        == question_data["answer"]
    )

    if is_correct:
        print("پاسخ درست است.")
    else:
        correct = question_data["answer"]
        print(
            f"پاسخ درست: {correct}) "
            f"{question_data['options'][correct]}"
        )

    print(
        f"توضیح: "
        f"{question_data['explanation']}"
    )

    return {
        "correct": is_correct,
        "user_answer": user_answer,
    }

این تابع خودش امتیاز کل را نگه نمی‌دارد. فقط نتیجه یک سؤال را برمی‌گرداند. این جداسازی باعث می‌شود تابع ساده‌تر و قابل استفاده مجدد باشد.

مرحله چهارم: ساخت سیستم امتیاز

برای اجرای کل آزمون، روی لیست سؤال‌ها حلقه می‌زنیم:

score = 0

total = len(QUESTIONS)

for index, question in enumerate(
    QUESTIONS,
    start=1,
):
    result = ask_question(
        question,
        index,
        total,
    )

    if result["correct"]:
        score += 1

تابع enumerate() هم خود سؤال را در اختیار ما می‌گذارد و هم شماره سؤال را تولید می‌کند. با start=1 شماره‌گذاری از یک شروع می‌شود.

مرحله پنجم: نگهداری پاسخ‌های اشتباه

نمایش فقط امتیاز نهایی کافی نیست. بهتر است کاربر بداند در کدام سؤال‌ها اشتباه کرده است.

wrong_answers = []

وقتی پاسخ غلط باشد:

wrong_answers.append({
    "question": question["question"],
    "your_answer": result["user_answer"],
    "correct_answer": question["answer"],
})

در پایان همین داده‌ها را برای مرور اشتباه‌ها استفاده می‌کنیم.

مرحله ششم: محاسبه درصد نتیجه

فرمول درصد پاسخ صحیح ساده است:

percentage = (
    score / total * 100
)

اما بهتر است حالت لیست خالی را هم مدیریت کنیم:

percentage = (
    score / total * 100
    if total
    else 0
)

تبدیل درصد به سطح

یک تابع جداگانه برای تفسیر نتیجه آموزشی می‌سازیم:

def get_level(percentage):
    if percentage >= 90:
        return "عالی"

    if percentage >= 70:
        return "خوب"

    if percentage >= 50:
        return "قابل قبول"

    return "نیاز به تمرین بیشتر"

این سطح‌بندی بخشی از منطق همین پروژه است و می‌توانید بازه‌ها را متناسب با آزمون خودتان تغییر دهید.

بانک سؤال کامل پروژه

QUESTIONS = [
    {
        "question": "خروجی print(2 ** 3) چیست؟",
        "options": {
            "A": "6",
            "B": "8",
            "C": "9",
            "D": "10",
        },
        "answer": "B",
        "explanation": (
            "عملگر ** برای توان است؛ "
            "2 به توان 3 برابر 8 می‌شود."
        ),
    },
    {
        "question": "کدام نوع داده تغییرپذیر است؟",
        "options": {
            "A": "tuple",
            "B": "str",
            "C": "list",
            "D": "int",
        },
        "answer": "C",
        "explanation": (
            "لیست در پایتون mutable است "
            "و می‌توان عناصر آن را تغییر داد."
        ),
    },
    {
        "question": (
            "تابع len([10, 20, 30]) "
            "چه مقداری برمی‌گرداند؟"
        ),
        "options": {
            "A": "2",
            "B": "3",
            "C": "30",
            "D": "60",
        },
        "answer": "B",
        "explanation": (
            "len تعداد عناصر لیست را برمی‌گرداند؛ "
            "این لیست سه عنصر دارد."
        ),
    },
    {
        "question": (
            "برای تعریف تابع از کدام "
            "کلمه کلیدی استفاده می‌شود؟"
        ),
        "options": {
            "A": "func",
            "B": "function",
            "C": "define",
            "D": "def",
        },
        "answer": "D",
        "explanation": (
            "تعریف تابع در پایتون "
            "با کلمه کلیدی def آغاز می‌شود."
        ),
    },
    {
        "question": "نتیجه 10 // 3 چیست؟",
        "options": {
            "A": "3",
            "B": "3.33",
            "C": "1",
            "D": "4",
        },
        "answer": "A",
        "explanation": (
            "// تقسیم صحیح است و بخش "
            "اعشاری نتیجه را حذف می‌کند."
        ),
    },
]

کد کامل آزمون چهارگزینه‌ای با پایتون

QUESTIONS = [
    {
        "question": "خروجی print(2 ** 3) چیست؟",
        "options": {
            "A": "6",
            "B": "8",
            "C": "9",
            "D": "10",
        },
        "answer": "B",
        "explanation": (
            "عملگر ** برای توان است؛ "
            "2 به توان 3 برابر 8 می‌شود."
        ),
    },
    {
        "question": "کدام نوع داده تغییرپذیر است؟",
        "options": {
            "A": "tuple",
            "B": "str",
            "C": "list",
            "D": "int",
        },
        "answer": "C",
        "explanation": (
            "لیست در پایتون mutable است."
        ),
    },
    {
        "question": (
            "تابع len([10, 20, 30]) "
            "چه مقداری برمی‌گرداند؟"
        ),
        "options": {
            "A": "2",
            "B": "3",
            "C": "30",
            "D": "60",
        },
        "answer": "B",
        "explanation": (
            "این لیست سه عنصر دارد."
        ),
    },
    {
        "question": (
            "برای تعریف تابع از کدام "
            "کلمه کلیدی استفاده می‌شود؟"
        ),
        "options": {
            "A": "func",
            "B": "function",
            "C": "define",
            "D": "def",
        },
        "answer": "D",
        "explanation": (
            "تعریف تابع با def آغاز می‌شود."
        ),
    },
    {
        "question": "نتیجه 10 // 3 چیست؟",
        "options": {
            "A": "3",
            "B": "3.33",
            "C": "1",
            "D": "4",
        },
        "answer": "A",
        "explanation": (
            "// عملگر تقسیم صحیح است."
        ),
    },
]


def get_answer(options):
    valid = set(options)

    while True:
        answer = input(
            "پاسخ شما (A/B/C/D یا Q برای خروج): "
        ).strip().upper()

        if answer == "Q":
            return None

        if answer in valid:
            return answer

        print(
            "گزینه نامعتبر است. یکی از "
            "A، B، C یا D را وارد کنید."
        )


def ask_question(
    question_data,
    number,
    total,
):
    print(
        f"\n--- سؤال {number} از {total} ---"
    )
    print(question_data["question"])

    for key, text in (
        question_data["options"].items()
    ):
        print(f"{key}) {text}")

    user_answer = get_answer(
        question_data["options"]
    )

    if user_answer is None:
        return None

    is_correct = (
        user_answer
        == question_data["answer"]
    )

    if is_correct:
        print("پاسخ درست است.")
    else:
        correct = question_data["answer"]
        print(
            f"پاسخ درست: {correct}) "
            f"{question_data['options'][correct]}"
        )

    print(
        f"توضیح: "
        f"{question_data['explanation']}"
    )

    return {
        "correct": is_correct,
        "user_answer": user_answer,
    }


def get_level(percentage):
    if percentage >= 90:
        return "عالی"

    if percentage >= 70:
        return "خوب"

    if percentage >= 50:
        return "قابل قبول"

    return "نیاز به تمرین بیشتر"


def run_quiz(questions):
    score = 0
    wrong_answers = []
    total = len(questions)

    for index, question in enumerate(
        questions,
        start=1,
    ):
        result = ask_question(
            question,
            index,
            total,
        )

        if result is None:
            print("آزمون متوقف شد.")
            return

        if result["correct"]:
            score += 1
        else:
            wrong_answers.append({
                "question": question["question"],
                "your_answer": result["user_answer"],
                "correct_answer": question["answer"],
            })

    percentage = (
        score / total * 100
        if total
        else 0
    )

    print("\n=== نتیجه نهایی ===")
    print(f"امتیاز: {score} از {total}")
    print(
        f"درصد پاسخ صحیح: "
        f"{percentage:.0f}%"
    )
    print(
        f"سطح: {get_level(percentage)}"
    )

    if wrong_answers:
        print("\nسؤال‌های اشتباه:")

        for item in wrong_answers:
            print(
                f"- {item['question']}"
            )
            print(
                f"  پاسخ شما: "
                f"{item['your_answer']}"
            )
            print(
                f"  پاسخ صحیح: "
                f"{item['correct_answer']}"
            )


def main():
    print(
        "=== آزمون چهارگزینه‌ای پایتون ==="
    )

    while True:
        run_quiz(QUESTIONS)

        again = input(
            "\nدوباره آزمون بدهید؟ (بله/خیر): "
        ).strip()

        if again != "بله":
            print("برنامه پایان یافت.")
            break


if __name__ == "__main__":
    main()

نمونه اجرای برنامه

=== آزمون چهارگزینه‌ای پایتون ===

--- سؤال 1 از 5 ---
خروجی print(2 ** 3) چیست؟
A) 6
B) 8
C) 9
D) 10
پاسخ شما: b
پاسخ درست است.
توضیح: عملگر ** برای توان است؛ 2 به توان 3 برابر 8 می‌شود.

بعد از سؤال آخر، برنامه خروجی‌ای شبیه این نمایش می‌دهد:

=== نتیجه نهایی ===
امتیاز: 4 از 5
درصد پاسخ صحیح: 80%
سطح: خوب

کد پروژه چطور تست شد؟

قبل از انتشار، Syntax کد و منطق تابع سطح نتیجه بررسی شد. برای نمونه، درصدهای ۱۰۰، ۸۰، ۵۵ و ۲۰ باید به‌ترتیب «عالی»، «خوب»، «قابل قبول» و «نیاز به تمرین بیشتر» برگردانند. بانک سؤال نمونه نیز پنج سؤال معتبر دارد.

اشتباه‌های رایج در ساخت Quiz پایتون

نوشتن سؤال‌ها داخل حلقه با ifهای طولانی

اگر هر سؤال را با کد جداگانه بنویسید، اضافه کردن سؤال جدید سخت می‌شود. بانک سؤال مبتنی بر داده این مشکل را حل می‌کند.

اعتماد مستقیم به ورودی کاربر

کاربر می‌تواند x، عدد یا حتی ورودی خالی وارد کند. پاسخ باید قبل از بررسی در محدوده گزینه‌های مجاز اعتبارسنجی شود.

مقایسه متن گزینه به‌جای کلید

ذخیره پاسخ درست با کلیدهایی مثل A و B مقایسه را ساده‌تر می‌کند و در صورت تغییر متن گزینه، منطق اصلی نمی‌شکند.

ذخیره امتیاز در متغیر Global

برای این پروژه نیازی به متغیر سراسری نیست. امتیاز می‌تواند داخل run_quiz() نگه داشته شود و با هر اجرای جدید از صفر شروع شود.

چطور پروژه را توسعه دهیم؟

  1. تصادفی کردن ترتیب سؤال‌ها: با random.shuffle() ترتیب بانک سؤال را تغییر دهید.
  2. تصادفی کردن گزینه‌ها: گزینه‌های هر سؤال را در هر اجرا جابه‌جا کنید.
  3. زمان‌سنج: برای هر سؤال محدودیت زمانی ایجاد کنید.
  4. سطح دشواری: سؤال‌ها را به آسان، متوسط و سخت تقسیم کنید.
  5. دسته‌بندی موضوعی: سؤال‌های Python Basics، OOP، File و Exception را جدا کنید.
  6. ذخیره سؤال‌ها در JSON: بانک سؤال را از کد خارج و در فایل JSON نگه دارید. آموزش کار با فایل JSON در پایتون برای این توسعه مفید است.
  7. ذخیره رکورد: بهترین درصد کاربر را در فایل ثبت کنید.
  8. نسخه گرافیکی: با Tkinter سؤال‌ها را در پنجره و گزینه‌ها را با Radio Button نمایش دهید.

تمرین پیشنهادی: تصادفی کردن سؤال‌ها

یک کپی از لیست سؤال‌ها بسازید و قبل از آزمون ترتیب آن را Shuffle کنید تا بانک اصلی تغییر نکند:

import random

quiz_questions = QUESTIONS.copy()
random.shuffle(quiz_questions)

run_quiz(quiz_questions)

در مرحله بعد تلاش کنید فقط ۳ سؤال تصادفی از بانک ۲۰ سؤالی انتخاب کنید.

تمرین پیشرفته: بارگذاری سؤال‌ها از JSON

ساختار بانک سؤال ما به‌راحتی قابل ذخیره در JSON است. این کار باعث می‌شود برای تغییر سؤال‌ها مجبور نباشید کد برنامه را ویرایش کنید. برنامه می‌تواند هنگام اجرا فایل سؤال‌ها را بخواند و لیست دیکشنری‌ها را بسازد.

این پروژه چه مفاهیمی را تمرین می‌دهد؟

  • List و Dictionary
  • Set و بررسی عضویت
  • حلقه for و while
  • enumerate
  • شرط‌ها
  • توابع و مقدار بازگشتی
  • اعتبارسنجی ورودی
  • محاسبه درصد
  • ساخت داده ساختاریافته
  • جداسازی منطق برنامه به چند تابع
  • مدیریت وضعیت یک آزمون چندمرحله‌ای

جمع‌بندی

در این آموزش یک آزمون چهارگزینه‌ای کامل با پایتون ساختیم که سؤال‌ها را از یک بانک ساختاریافته می‌خواند، گزینه‌ها را نمایش می‌دهد، ورودی کاربر را کنترل می‌کند، پاسخ را بررسی می‌کند، امتیاز و درصد را محاسبه می‌کند و در پایان سؤال‌های اشتباه را برای مرور نشان می‌دهد.

نکته اصلی این پروژه فقط ساخت Quiz نیست؛ بلکه یاد می‌گیریم داده و منطق برنامه را از هم جدا کنیم. همین الگو در پروژه‌های بزرگ‌تر مثل سیستم آزمون آنلاین، نرم‌افزار آموزشی یا بازی‌های سؤال و جواب هم کاربرد دارد.

📌 همچنین بخوانید: تمرین پایتون با جواب، ساخت بازی با پایتون و Pygame و ساخت ربات با پایتون

می‌خواهی پیشرفتت ذخیره شود؟با ورود یا ثبت‌نام رایگان، درس‌های تمام‌شده را علامت بزن و درصد پیشرفتت را در حساب کاربری ببین.ورود / ثبت‌نام برای ذخیره پیشرفت
درس بعدی · پروژه‌های پایتون (8 از 19)ساخت شبیه‌ساز تاس با پایتون؛ پروژه کامل با random، چند تاس و آمار نتایج ←
آموزش‌های تازه، کد آماده و رفع خطاهر هفته در کانال Dev Source؛ رایگان.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *