Сегодня мы создадим тесты на Python. Но не автотесты, с помощью которых можно проверить работоспособность программы, а тесты для проверки знаний, когда нужно выбрать один или несколько правильных ответов.
Если вы только интересуетесь Python и начинаете его изучать, посмотрите наш мастрид, где мы собрали всё самое важное и интересное для старта в этом языке программирования.
Для работы нам понадобится Python. Если его ещё у вас нет — можно установить за 10 минут:
Логика проекта
Мы создадим скрипт, который будет выводить в консоль текст вопроса, предлагать несколько вариантов ответов и просить ввести номера ответов через запятую. После этого программа сообщит, правильно или неправильно мы ответили, а в конце покажет, сколько всего было вопросов и сколько нам удалось решить.
Что используем
Для создания нашего скрипта нам используем несколько базовых возможностей Python:
- списки, множества, словари и кортежи — коллекции данных;
- циклы;
- встроенные функции
split
,map
иenumerate
.
Список — это универсальная изменяемая упорядоченная коллекция элементов, каждый из которых имеет свой номер — индекс. В списках обозначаются квадратными скобками и могут хранить данные любого типа:
my_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Множество — изменяемая коллекция уникальных и неупорядоченных элементов. Множества обозначаются фигурными скобками. Если записать внутри несколько одинаковых элементов, ошибки не будет, но дубликаты не сохранятся:
my_set = {1, 9, 8, 8, 8, 5}
# выведет {1, 9, 8, 5}
print(my_set)
Само множество можно изменять, например добавить или удалить элементы. Но внутри множества могут быть только неизменяемые типы данных, например строки или целые числа. Поэтому список во множество положить нельзя. Но можно сделать множество из списка, если применить к нему команду set
.
Словарь хранит коллекции элементов в парах вида «ключ : значение». Ключи должны быть уникальным и неизменяемым типом данных, а значения могут повторяться и быть любыми. Словарь тоже обозначается фигурным скобками, но внутри него будут пары:
my_dict = {'русский': 5, 'математика': 3, 'география': 4}
Кортеж — неизменямый список. Для него нельзя применить операции, которые обновят коллекцию. Обозначается круглыми скобками:
my_tuple = (99, 46, 13)
Циклы выполняют определённое действие указанное количество раз. Ещё они позволяют проходить по элементам словарей. Можно вывести значение на экран, когда доходим до нужного ключа.
Функция-метод .split()
применяется к строкам и разбивает их в указанных местах, например на пробелах или запятых. Получается список с несколькими элементами.
Map()
принимает в скобках два аргумента: функцию и какой-то объект, элементы которого нужно перебрать. При запуске проходит по этим элементам и к каждому применяет заданную функцию. В результате получается список.
# создаём функцию
def func(x):
return x**3
# создаём список
a = [1, 3, 5, 7]
# применяем к списку a функцию func внутри функции map
b = list(map(func, a))
# выведет [1, 27, 125, 343]
print(b)
Enumerate()
можно применить к итерируемым объектам и создать для каждого элемента кортеж с его номером и самим элементом. Первый номер можно задать любой:
# создаём список
seasons = ['Весна', 'Лето', 'Осень', 'Зима']
# применяем к нему enumerate и создаём список из кортежей, начиная с номера 1
new_list = list(enumerate(seasons, start=1))
# выведет [(1, 'Весна'), (2, 'Лето'), (3, 'Осень'), (4, 'Зима')]
print(new_list)
Создаём словарь
В нашем тесте будет несколько вопросов. У каждого вопроса есть:
- номер или название;
- текст вопроса;
- текст задания, где написано, что нужно сделать;
- варианты ответов;
- правильные ответы.
Все вопросы будут находиться внутри одного большого словаря. Ключом будет строка с названием или номером вопроса, например “Задание 1 – Синтаксический анализ предложений”
. В значение мы положим ещё один словарь, в котором будет несколько пар для остальных частей вопроса.
Словарь с двумя заданиями выглядит так:
# создаём один словарь со всеми тестами и ответами
tests = {
# ключ первой пары “ключ — значение”
"Задание 1 - Синтаксический анализ предложений":
# значение первой пары “ключ — значение” — ещё один словарь
{
# текст вопроса. первая пара словаря, ключ — 'текст', значение — текст в несколько строк
"текст": (
"\n(1)Я обернулся, заканчивая осмотр островка, и ахнул от изумления:"
"\nметрах в сорока от меня вставал из песка замок, совсем крошечный, "
"\nочень аккуратный, жмущийся к берегу и нависающий над самой водой. "
"\n(2)Но у него было всё полагающееся настоящему замку: высокие стены"
"\nиз розового мрамора, сторожевая башня метров десяти-пятнадцати в высоту, "
"\nузкие окна-бойницы, ворота из серого металла. (3)Однако это было ещё не самое удивительное."
"\n(4)С трёх сторон замка, обращённых к морю, ровными дугами выгибались тонкие розовые мосты."
"\n(5)Они шли над морем, поднимаясь на головокружительную высоту,"
"\nи опускались на островках вдали, кажется, возле таких же замков."
),
# текст задания. вторая пара словаря, ключ — 'вопрос', значение — текст вопроса
"вопрос": "Прочитайте текст. Укажите варианты ответов, в которых дано верное утверждение.",
# варианты ответов. третья пара словаря, ключ — 'варианты', значение — варианты ответов
"варианты": [
"1-е предложение – бессоюзное сложное предложение, двоеточие ставится, "
"\nпотому что вторая часть указывает на причину того, о чем говорится в первой части",
"2-е предложение – простое предложение с обобщающим словом"
"\nпри однородных членах, поэтому ставится двоеточие",
"3-е предложение – односоставное безличное",
"4-е предложение – простое, ничем не осложнено",
"5-е предложение – простое, осложнено обособленным определением"
],
# правильные ответы. четвёртая пара словаря, ключ — 'правильные ответы',
# значение — список с правильными номерами ответов
"правильные_ответы": [1, 2]
},
# следующий вопрос
"Задание 2": {
"текст": (
"\nНайдите слова, где пропущена одна и та же буква"
),
"вопрос": "Выберите верные номера",
"варианты": [
"волну..мый, выгоня..шь",
"невид..мый (берег), подброс..шь",
"разбуд..шь, сменя..мый",
"бормоч..шь, нагруж..нный",
"дыш..шь, увид..нный"
],
"правильные_ответы": [1, 2, 4]
}
}
Так можно добавить любое количество заданий.
Выглядит громоздко, но нам важно только помнить правильные ключи. По ним мы быстро найдём нужные значения, которые надо вывести на экран.
Мы не указали отдельно номера в ответах. Для этого используем функцию enumerate()
.
Выводим задание на экран
Чтобы задание отобразилось на экране, мы напишем функцию, которая принимает в качестве аргумента словарь со всеми заданиями:
def display_question(test)
Название вопроса потом выведем отдельно, а здесь начнём с текста задания. Для этого берём наш словарь и передадим на экран значение по ключу ‘текст’
. После этого переставим курсор на новую строку командой \n
:
print(f"Текст: {test['текст']}\n")
Для удобства мы передаём значение переменной прямо в строке. Для этого перед строкой надо поставить строчную букву f
.
После вопроса надо вывести текст задания. Используем тот же подход и ищем значение по ключу ‘вопрос’
:
print(f"Вопрос: {test['вопрос']}\n")
Осталось вывести правильные варианты. Они хранятся в виде списка, поэтому сначала найдём его, используя ключ ‘варианты’
. Потом пройдёмся по списку циклом, к каждому ответу применим enumerate()
и выведем всё по номерам, начиная с 1:
for i, option in enumerate(test['варианты'], 1):
print(f"{i}. {option}")
Так функция выглядит полностью:
# создаём функцию для вывода вопросов в консоли
def display_question(test):
# выводим текст каждого отдельного задания — для этого
# находим внутри словаря test значение ключа 'текст'
print(f"Текст: {test['текст']}\n")
# выводим вопрос каждого отдельного задания — для этого
# находим внутри словаря test значение ключа 'вопрос'
print(f"Вопрос: {test['вопрос']}\n")
# выводим пронумерованные варианты ответов каждого отдельного задания —
# для этого находим внутри словаря test значение ключа 'варианты', по которому лежит список,
# проходим по этому списку, нумеруем каждый элемент от 1 и выводим все элементы вместе с номером
for i, option in enumerate(test['варианты'], 1):
print(f"{i}. {option}")
Ввод ответа от пользователя реализуем отдельно, а сейчас напишем функцию для проверки.
Проверяем ответы
Мы создадим функцию, которая будет принимать строку с ответами пользователя и список с правильными ответами, которые уже лежат в основном словаре. Кладём это значение в общий массив с вопросами:
"правильные_ответы": [1, 2]
Сверять удобнее не списки, а множества: если в каком-то из них ответы будут идти в разном порядке, это не скажется на результате.
Поэтому с ответами пользователя поступим так:
- функцией
split
получим список из элементов-строк, которые изначально разделены запятыми; - при помощи
map
присвоим этим элементам тип данныхint
, то есть целое число; - создадим из целых чисел множество функцией set.
Так выглядит код для этой операции из трёх этапов:
user_answers_set = set(map(int, user_answers.split(',')))
С правильными ответами проще. У нас уже есть список, можно применить к нему функцию set и получить множество:
correct_answers_set = set(correct_answers)
Возвращать функция будет результат сравнения этих двух множеств. Если они совпадают, результатом будет True, если нет — False.
Функция полностью:
# создаём функцию для проверки ответов
def check_answers(user_answers, correct_answers):
# разбиваем строку ответа пользователя в тех местах, где есть запятая, и создаём из этих элементов список
# каждый элемент списка делаем целым числом, делаем из списка множество
user_answers_set = set(map(int, user_answers.split(',')))
# загружаем в переменную с правильными ответами множество, созданное из списка правильных ответов в словаре
correct_answers_set = set(correct_answers)
# проверяем, равны ли множество ответов пользователя и множество ответов из словаря
return user_answers_set == correct_answers_set
Осталось связать все функции в один процесс и кое-что добавить.
Собираем всё в одну функцию
Нам не хватает подсчёта правильных ответов. Создадим главную функцию и в начале объявим две переменные — для правильных ответов и вообще всех отвеченных:
def main():
right_answers = 0
all_questions = 0
Чтобы перебирать пары «ключ : значение» в словарях, используется метод .items()
. Мы применим его к словарю через цикл:
for name, test in tests.items():
Теперь name
будет присваиваться всем ключам по очереди, а test
— значениям.
После этого будем по очереди вызывать наши функции:
- Сначала выведем в консоль название вопроса.
- Потом текст задания и варианты ответов — для этого у нас есть функция
display_question
. - Просим ввести ответы через запятую и сохраняем эту строку в переменную
user_answers
. - Запускаем функцию проверки ответов
check_answers
. Сообщаем о результате и увеличиваем счётчик пройденных заданий. Если ответ верный, счётчик правильных ответов тоже. - Когда цикл закончился, отчитываемся о результат и предлагаем пройти тест ещё раз.
![Пишем на Python тесты для проверки знаний](https://thecode.media/wp-content/uploads/2024/06/unnamed-15-1.png)
Код основной функции полностью:
# создаём основную функцию
def main():
# создаём счёчик правильных ответов
right_answers = 0
# создаём счёчик всех ответов
all_questions = 0
# проходим циклом по ключам и значениям в основном словаре
for name, test in tests.items():
# выводим номер или название задания
print(f"\n{name}")
# выводим текст задания и варианты ответов
display_question(test)
# просим пользователя ввести ответы
user_answers = input("\nВведите номера верных ответов через запятую: ")
# проверяем ответы отдельной функцией, пишем о результате
if check_answers(user_answers, test['правильные_ответы']):
print("Верно!")
# если пользователь ответил верно, увеличиваем счётчик верных ответов
right_answers += 1
else:
print("Неверно!")
# увеличиваем счётчик всех ответов в любом случае
all_questions += 1
# отчитываемся о результатах пройденного теста и предлагаем пройти ещё раз
print(f'Вы ответили правильно на {right_answers} вопросов из {all_questions}.'
f'\nХотите попробовать ещё раз? (Да/Нет)')
# ждём, что овтетит пользователь
one_more_test = input('')
# запускаем тест повторно, если пользователь отвечает 'Да'
if one_more_test == 'Да':
main()
Что улучшим в следующий раз
Сегодня мы все тесты вписали прямо в тело кода. В следующий раз сделаем так: создадим текстовый или вордовский файл и добавим возможность загружать тесты оттуда. Так можно будет брать стандартные школьные или институтские тесты и проходить их, как на экзамене.
# создаём один словарь со всеми тестами и ответами
tests = {
# ключ первой пары ключ-значение
"Задание 1 - Синтаксический анализ предложений":
# значение первой пары ключ-значение — ещё один словарь
{
# текст вопроса. первая пара словаря, ключ — 'текст', значение -- текст в несколько строк
"текст": (
"\n(1)Я обернулся, заканчивая осмотр островка, и ахнул от изумления:"
"\nметрах в сорока от меня вставал из песка замок, совсем крошечный, "
"\nочень аккуратный, жмущийся к берегу и нависающий над самой водой. "
"\n(2)Но у него было всё полагающееся настоящему замку: высокие стены"
"\nиз розового мрамора, сторожевая башня метров десяти-пятнадцати в высоту, "
"\nузкие окна-бойницы, ворота из серого металла. (3)Однако это было ещё не самое удивительное."
"\n(4)С трёх сторон замка, обращённых к морю, ровными дугами выгибались тонкие розовые мосты."
"\n(5)Они шли над морем, поднимаясь на головокружительную высоту,"
"\nи опускались на островках вдали,кажется, возле таких же замков."
),
# текст задания. вторая пара словаря, ключ — 'вопрос', значение — текст вопроса
"вопрос": "Прочитайте текст. Укажите варианты ответов, в которых дано верное утверждение.",
# варианты ответов. третья пара словаря, ключ — 'варианты', значение — варианты ответов
"варианты": [
"1-е предложение – бессоюзное сложное предложение, двоеточие ставится, "
"\nпотому что вторая часть указывает на причину того, о чём говорится в первой части",
"2-е предложение – простое предложение с обобщающим словом"
"\nпри однородных членах, поэтому ставится двоеточие",
"3-е предложение – односоставное безличное",
"4-е предложение – простое, ничем не осложнено",
"5-е предложение – простое, осложнено обособленным определением"
],
# правильные ответы. четвёртая пара словаря, ключ — 'правильные ответы',
# значение — список с правильными номерами ответов
"правильные_ответы": [1, 2]
},
# следующий вопрос
"Задание 2": {
"текст": (
"\nНайдите слова, где пропущена одна и та же буква"
),
"вопрос": "Выберите верные номера",
"варианты": [
"волну..мый, выгоня..шь",
"невид..мый (берег), подброс..шь",
"разбуд..шь, сменя..мый",
"бормоч..шь, нагруж..нный",
"дыш..шь, увид..нный"
],
"правильные_ответы": [1, 2, 4]
}
}
# создаём функцию для вывода вопросов в консоли
def display_question(test):
# выводим текст каждого отдельного задания — для этого
# находим внутри словаря test значение ключа 'текст'
print(f"Текст: {test['текст']}\n")
# выводим вопрос каждого отдельного задания — для этого
# находим внутри словаря test значение ключа 'вопрос'
print(f"Вопрос: {test['вопрос']}\n")
# выводим пронумерованные варианты ответов каждого отдельного задания —
# для этого находим внутри словаря test значение ключа 'варианты', по которому лежит список,
# проходим по этому списку, нумеруем каждый элемент от 1 и выводим все элементы вместе с номером
for i, option in enumerate(test['варианты'], 1):
print(f"{i}. {option}")
# создаём функцию для проверки ответов
def check_answers(user_answers, correct_answers):
# разбиваем строку ответа пользователя в тех местах, где есть запятая, и создаём из этих элементов список
# каждый элемент списка делаем целым числом, делаем из списка множество
user_answers_set = set(map(int, user_answers.split(',')))
# загружаем в переменную с правильными ответами множество, созданное из списка правильных ответов в словаре
correct_answers_set = set(correct_answers)
# проверяем, равны ли множество ответов пользователя и множество ответов из словаря
return user_answers_set == correct_answers_set
# создаём основную функцию
def main():
# создаём счёчик правильных ответов
right_answers = 0
# создаём счёчик всех ответов
all_questions = 0
# проходим циклом по ключам и значениям в основном словаре
for name, test in tests.items():
# выводим номер или название задания
print(f"\n{name}")
# выводим текст задания и варианты ответов
display_question(test)
# просим пользователя ввести ответы
user_answers = input("\nВведите номера верных ответов через запятую: ")
# проверяем ответы отдельной функцией, пишем о результате
if check_answers(user_answers, test['правильные_ответы']):
print("Верно!")
# если пользователь ответил верно, увеличиваем счётчик верных ответов
right_answers += 1
else:
print("Неверно!")
# увеличиваем счётчик всех ответов в любом случае
all_questions += 1
# отчитываемся о результатах пройденного теста и предлагаем пройти ещё раз
print(f'Вы ответили правильно на {right_answers} вопросов из {all_questions}.'
f'\nХотите попробовать ещё раз? (Да/Нет)')
# ждём, что овтетит пользователь
one_more_test = input('')
# запускаем тест повторно, если пользователь отвечает 'Да'
if one_more_test == 'Да':
main()
main()