Перейти к содержимому
Ступень 00.1 Рабочее окружение

Как устроены домашние задания и проверки

Квизы, задания в браузере, домашние работы с автотестами и уровнями Simple → Boss, ИИ-ревьюер — как всё это работает и как с этим учиться.

УРОКstage-0/0-1-environment/03-how-homework-works.mdx1 чУрок 3 из 3
После урока вы сможете
  • Ориентироваться в структуре homework/<id>/ — где писать код, где лежат тесты и эталон
  • Запускать автогрейдер python homework/check.py <id> и читать отчёт по уровням Simple / Medium / Strong / Boss
  • Читать упавший тест как подсказку и отлаживать решение по сообщению assert
  • Просить ИИ-ревьюера проверить решение так, чтобы он учил, а не решал за вас

Вы учитесь сами, по вечерам, без преподавателя. Значит, кто-то другой должен сказать вам «верно» или «вот здесь ошибка», причём сразу, а не через неделю. В курсе эту роль играют автоматические проверки. Квиз проверяет понимание, задание в браузере — что вы умеете превратить идею в код, а домашняя работа с автотестами — что код работает и на краевых случаяхкраевой случайEN edge caseНеобычный вход на границе допустимого — пустой список, ноль, пустая строка. Именно на них чаще всего ломается код. (необычных входных данных: пустой текст, ноль, один элемент, очень длинная строка) и не тормозит.

Устроено это по образцу лучших курсов по LLM. Тесты, которые сначала все красные, взяты из Stanford CS336. Ступенчатые уровни Simple / Medium / Strong / Boss — из курсов Ли Хунъи в Национальном университете Тайваня. Промпт для ИИ-ревьюера — из задач Фуданьского университета (nndl/llm-beginner). За 15 минут разберёмся, как всем этим пользоваться, и решим первое задание.

ЧтоГдеКак проверяетсяЗачем
Квиз «Проверь себя»в конце урокасразу, с объяснением. Зачёт — от 60 %, пересдавать можно сколько угоднопонимание идей
Задание с кодомна странице урока, Python в браузеретесты прямо на страницепервая реализация идеи
Домашняя работав репозитории, папка homework/автотесты pytest + уровниполноценный код, краевые случаи, скорость
Карточкистраница «Повторение»интервальное повторение: алгоритм FSRS сам решает, когда показать карточку сновачтобы не забыть через месяц
Тетрадь ошибокстраница «Тетрадь ошибок»сюда автоматически попадает каждый неверный ответ в квизеповторять именно то, что пока не понято

Ещё одна китайская привычка — отметка о занятиях (дословно «пробить карточку»): календарь дней занятий на странице «Карта курса и прогресс». Цель — не рекорды, а регулярность: несколько вечеров в неделю.

flowchart LR
L["Урок"] --> Q["Квиз ≥ 60%"]
L --> E["Задание в браузере"]
Q --> D["Отметить урок пройденным"]
E --> D
D --> C["Карточки → повторение"]
D --> H["ДЗ модуля<br/>python homework/check.py"]
H --> M{"Уровень ≥ Medium?"}
M -- "да" --> N["Следующая ступень"]
M -- "нет" --> R["Подсказки, ИИ-ревью, ещё попытка"]
R --> H
Как проходит модуль

Python в браузере работает благодаря Pyodide — обычному Python, пересобранному в формат WebAssembly, который браузер умеет выполнять почти с родной скоростью. Код выполняется на вашем компьютере, а не на сервере. Поэтому задания работают без регистрации, а ваш черновик сохраняется в браузере автоматически. С таким окном вы уже встречались в уроке «Как компьютер выполняет программу».

Кнопки задания:

  • ▶ Запустить (Ctrl+Enter) — просто выполнить код и показать, что напечатал print.
  • ✓ Проверить (Shift+Enter) — прогнать тесты. Под каждым тестом — его описание и сообщение об ошибке.
  • 💡 Подсказка — открывает подсказки по одной, от общей к почти готовой.
  • ↺ Сбросить — вернуть исходную заготовку.
  • 👁 Решение — появляется после первой проверки. Смотрите его после честной попытки, а не вместо неё.

Ограничения: в браузере доступны чистый Python и научные пакеты, собранные для Pyodide: NumPy, а в следующих ступенях pandas, scikit-learn, SciPy. Такой пакет при первом запуске скачивается из интернета. PyTorch в браузере не работает, поэтому задачи на нём живут в домашних работах.

Python по-настоящему начнётся в модуле 0.2. Но в этом уроке встретится код с функциями, и для первого задания хватит четырёх вещей. Прочитайте код и комментарии — всё, что после #, Python пропускает:

def double(x): # def — «определить функцию» с именем double и одним аргументом x
return x * 2 # return — что функция вернёт. Отступ в 4 пробела обязателен!
print(double(21)) # вызываем функцию с аргументом 21 → печатается 42
name = "Аня"
print(f"Привет, {name}!") # f-строка: значение name подставляется в фигурные скобки → Привет, Аня!
print(3 >= 2, 2 >= 3) # сравнение «больше или равно» даёт True (истина) или False (ложь)
print(100 * 3 / 5) # деление / всегда даёт дробное число → 60.0
42
Привет, Аня!
True False
60.0

Функция — это именованный кусок кода, маленькая «машинка»: получает значения (аргументы) и возвращает результат через return. Тесты вызывают ваши функции с разными аргументами и сравнивают результат с ожидаемым.

Ошибки, которые вы почти наверняка увидите в первый раз, и что они значат:

СообщениеЧто случилосьКак исправить
IndentationError: expected an indented block after function definitionстрока после def ...: без отступапоставьте 4 пробела перед return
SyntaxError: expected ':'забыли двоеточие в конце строки defdef greet(name):
SyntaxError: unterminated string literalне закрыта кавычка"Привет, {name}!" — кавычка в начале и в конце
NameError: name 'nmae' is not definedопечатка в имениимя должно в точности совпадать с аргументом
тест пишет «вернула None»нет return: функция что-то посчитала, но ничего не вернуланачните строку с return
тест пишет «вернула Ellipsis»в коде осталось ... из заготовкизамените многоточие своим выражением

Этого достаточно, чтобы решить задание в конце урока. Разделы ниже про домашние работы с автотестами пригодятся к концу ступени 0, когда дойдёт до первой домашней работы: сейчас прочитайте их для общего представления.

Каждая домашняя работа — папка в репозитории курса:

homework/s0-hw01-python-warmup/
├── README.md # условие: что сделать, уровни, подсказки, промпт для ИИ-ревью, источники
├── task.yaml # метаданные: ступень, модуль, файлы, нужен ли GPU, сколько часов
├── code/ # ← ЗДЕСЬ вы пишете решение
│ └── textstats.py
└── tests/ # тесты — не меняйте их
├── conftest.py
└── test_textstats.py
solutions/s0-hw01-python-warmup/ # эталонное решение

В code/ лежит заготовка: функции с готовыми сигнатурами и докстрингами, но без тела.

def tokenize(text: str) -> list[str]:
"""Разбить текст на слова: нижний регистр, только буквы и цифры (кириллица и латиница).
Пример: "Привет, мир! Hello world" -> ["привет", "мир", "hello", "world"]
"""
raise NotImplementedError

Запись text: str и -> list[str] — это подсказки типов: аргумент text — строка, а функция возвращает список строк. Python их не проверяет, они для людей и редактора. Тройные кавычки """...""" — докстринг, описание функции с примером.

Сигнатуру (имя функции, аргументы) менять нельзя: по ней тесты вызывают ваш код. Внутри можно делать что угодно — добавлять вспомогательные функции, импортировать стандартную библиотеку и NumPy. Команда raise NotImplementedError сразу останавливает функцию с ошибкой «ещё не реализовано». Пока тело функции — только эта строка, все тесты красные. Так и задумано, как в CS336: вы превращаете красное в зелёное.

модульный тестмодульный тест (юнит-тест)EN unit testМаленькая функция, которая вызывает ваш код и проверяет результат через assert. — это маленькая функция, которая вызывает ваш код и проверяет результат через assert:

def test_simple_tokenize_basic():
assert tokenize("Привет, мир! Hello world") == ["привет", "мир", "hello", "world"]

assert условие значит «здесь обязано быть верно». Если условие после assert ложно, Python выбрасывает AssertionError, и тест считается проваленным. Тесты запускает pytest — библиотека для тестов, которую вы поставили из homework/requirements.txt в прошлом уроке. Она находит файлы test_*.py, а в них — функции test_*, выполняет каждую и собирает отчёт. Префикс имени задаёт уровень: test_simple_*, test_medium_*, test_strong_*, test_boss_*.

Тест — это ещё и спецификация: он заставляет решить, как функция должна вести себя в неочевидном случае. Создайте файл my_first_test.py:

def greet(name):
return f"Привет, {name}!"
def test_greet():
assert greet("Аня") == "Привет, Аня!"
def test_greet_empty():
assert greet("") == "Привет!"

и запустите python -m pytest my_first_test.py -q. Конец вывода будет таким:

def test_greet_empty():
> assert greet("") == "Привет!"
E AssertionError: assert 'Привет, !' == 'Привет!'
E
E - Привет!
E + Привет, !
E ? ++
my_first_test.py:10: AssertionError
FAILED my_first_test.py::test_greet_empty - AssertionError: assert 'Привет, !...
1 failed, 1 passed in 0.02s

Первый тест прошёл, второй нашёл краевой случай, о котором вы не подумали: пустое имя даёт «Привет, !». pytest даже показал, чем строки различаются: знаки ++ под лишними символами. Теперь нужно решить, что правильно, и поправить либо функцию, либо тест. Ровно такие краевые случаи и проверяют уровни Strong в домашних работах.

Команды запускаются из корня репозитория с активированным окружением:

Окно терминала
python homework/check.py s0-hw01-python-warmup # проверить своё решение
python homework/check.py s0-hw01 # можно писать начало id
python homework/check.py s0-hw01 -v # подробный вывод pytest с трассировкой
python homework/check.py --list # все ДЗ и ваш лучший уровень по каждому

Пример отчёта на частично готовом решении. Тесты в вашей версии ДЗ могут отличаться, важен формат:

━━━ s0-hw01-python-warmup: Разминка на Python — анализ русского и английского текста ━━━
🥉 Simple: 4/4
✓ test_simple_tokenize_basic
✓ test_simple_tokenize_digits_and_yo
✓ test_simple_top_words
✓ test_simple_bigrams
🥈 Medium: 0/3
✗ test_medium_top_words_ties_alphabetical
→ AssertionError: assert [('в', 1), ('б', 1), ('а', 1)] == [('а', 1), ('б', 1), ('в', 1)]
✗ test_medium_char_counts
→ NotImplementedError
✗ test_medium_cyrillic_ratio
→ NotImplementedError
🥇 Strong: 0/1
✗ test_strong_real_text
→ NotImplementedError
🐉 Boss: 1/1
✓ test_boss_performance
Итог: 🥉 Simple — задание зачтено ✅
Для перехода на следующую ступень нужен уровень 🥈 Medium.

Обратите внимание: Boss-тест на скорость прошёл, но итог — Simple. Правило такое: уровень засчитывается, только если пройдены все тесты этого уровня и всех уровней ниже. Нельзя получить Boss, не доделав Medium. Лучший результат автогрейдер сохраняет в homework/.results.json. Этот файл стоит в .gitignore, поэтому в истории Git ваш прогресс хранят коммиты с решениями: уровень всегда можно восстановить, снова запустив check.py.

УровеньЧто проверяетЧто это значит для вас
🥉 Simpleбазовая корректность на простых примерахзадание зачтено, идея понята
🥈 Mediumполный функционал, все требования условиянужно для перехода на следующую ступень
🥇 Strongкраевые случаи, устойчивость, качествовы пишете код, которому можно доверять
🐉 Bossскорость, масштаб или исследовательская частьуровень выше ожидаемого, по желанию

Задания со ступени 5, которым нужен GPU, проверяют Boss по артефакту: ваш скрипт обучения сохраняет artifacts/metrics.json, а тест сверяет метрики с порогом. Если файла нет, Boss-тест просто пропускается.

Сообщение assert — это подсказка, а не приговор. Разберём строку из отчёта выше:

AssertionError: assert [('в', 1), ('б', 1), ('а', 1)] == [('а', 1), ('б', 1), ('в', 1)]

Слева — что вернул ваш код, справа — что ожидалось. Слова те же, частоты те же, отличается только порядок при равных частотах. Имя теста (ties_alphabetical) прямо говорит, какой порядок нужен. Как это исправить, оставим вам: это часть ДЗ.

Алгоритм отладки:

  1. Прочитайте имя теста и сообщение: что ожидалось и что получено.
  2. Откройте файл теста и найдите этот тест. Там видны точные входные данные.
  3. Воспроизведите вызов вручную в Python или Jupyter: from textstats import top_words (из папки code/), затем top_words("в б а", 3).
  4. Добавьте print внутрь функции или запустите только этот тест подробно: python -m pytest homework/s0-hw01-python-warmup/tests -k ties -v.
  5. Исправьте код и снова запустите check.py. Остальные тесты не должны «покраснеть».
Что видитеПочемуЧто делать
pytest не запустился. Установлены ли зависимости?окружение не активировано или в нём нет pytestsource .venv/bin/activate, затем pip install -r homework/requirements.txt
can't open file '.../homework/check.py'вы не в корне репозиторияcd в папку ChinaAI (проверьте ls: там должны быть homework/ и site/)
ДЗ «…» не найденоопечатка в idpython homework/check.py --list покажет все id
все тесты падают с NotImplementedError, хотя код написанправите не тот файл, например копию в solutions/решение должно лежать в homework/<id>/code/
проверка не заканчиваетсябесконечный цикл в вашем кодеCtrl+C, затем запустите с -v, чтобы увидеть, на каком тесте зависло

Языковая модель отлично проверяет код. Но если попросить «реши задание», вы получите решение и не получите навык. Поэтому в README каждой домашней работы есть раздел «ИИ-ревью» с готовым промптом. Идея взята из задач Фуданьского университета (файл eval/tutor_prompt.md в nndl/llm-beginner). Общий шаблон:

Ты — преподаватель курса ChinaAI. Проверь моё решение домашней работы <hw-id>.
Условие и рубрика (из README.md):
<вставьте раздел «Что нужно сделать» и таблицу уровней>
Мой код:
<вставьте файлы из code/>
Вывод автогрейдера:
<вставьте вывод python homework/check.py <hw-id>>
Правила:
1. Не пиши за меня исправленный код и не давай готового решения.
2. Оцени решение по рубрике: корректность, краевые случаи, читаемость, эффективность.
3. Назови 1–3 самые важные проблемы через наводящие вопросы или подсказки.
4. Задай мне 3 вопроса на понимание моего же решения.
5. Отвечай по-русски.

Подойдёт любой сильный ассистент: Claude, DeepSeek, Qwen. Отвечать на вопросы ревьюера полезнее, чем читать его похвалу. Если не смогли ответить, значит, тема понята не до конца.

Ваше первое задание (всё нужное для него — в разделе «Минимум Python для этого урока» выше). Оно простое, но проходит весь путь: прочитать условие, написать код, запустить, проверить, разобраться с красными тестами. Нажмите ▶ Запустить сразу: пока вместо ответов печатается Ellipsis. Заменяйте ... по одному и запускайте снова. Когда все строки печатают то, что нужно, нажмите ✓ Проверить.

Hello, проверка: приветствие и процент квиза6 тестов
# Первое задание курса. Функция — это «машинка»: получает значения в скобках
# (аргументы) и возвращает результат командой return.
#
# Готовый пример — посмотрите, как устроена функция:
def double(x):
    return x * 2  # double(21) вернёт 42


# 1. Приветствие: greet("Аня") должна вернуть строку "Привет, Аня!"
#    f-строка f"Привет, {name}!" подставляет значение name внутрь текста.
def greet(name):
    return ...  # TODO: замените ... (многоточие) на f-строку


# 2. То же с кавычками-«ёлочками»: greet_quoted("Аня") -> "Привет, «Аня»!"
#    Внимание: «ёлочки» « и » — другие символы, не обычная кавычка ".
#    Скопируйте их прямо отсюда.
def greet_quoted(name):
    return ...  # TODO


# 3. Процент правильных ответов в квизе: quiz_percent(3, 5) -> 60.0
def quiz_percent(correct, total):
    return ...  # TODO: 100 * correct / total


# 4. Зачтён ли квиз: порог — 60 % и выше.
#    quiz_passed(3, 5) -> True,  quiz_passed(2, 5) -> False
#    Сравнение >= («больше или равно») само даёт True или False.
def quiz_passed(correct, total):
    return ...  # TODO: вызовите quiz_percent(correct, total) и сравните с 60


# Кнопка «▶ Запустить» выполнит строки ниже — так вы увидите свои результаты до тестов:
print(double(21))
print(greet("Аня"))
print(greet_quoted("Аня"))
print(quiz_percent(3, 5), quiz_passed(3, 5), quiz_passed(2, 5))

Это задание пока сложнее, чем нужно: в нём словари, циклы и условия из модуля 0.2. Вернитесь сюда после урока «Переменные, типы, условия, циклы» — так работает спиральная программа: тема возвращается, когда вы готовы взять её на новом уровне. Вы запрограммируете то самое правило, по которому автогрейдер курса выставляет уровень.

⭐ Правило уровней автогрейдера4 теста
LEVELS = ["simple", "medium", "strong", "boss"]


def best_level(passed):
    """Лучший уровень по правилу автогрейдера курса (homework/check.py).

    passed — словарь вида {"simple": True, "medium": False, ...}:
    True, если ВСЕ тесты этого уровня пройдены.

    Уровни идут по порядку LEVELS: simple → medium → strong → boss.
    - уровень засчитывается, только если пройден он сам и все уровни ниже;
    - уровня нет в словаре (в задании нет таких тестов) — просто пропускаем его;
    - на первом непройденном уровне останавливаемся;
    - если не засчитан ни один уровень — вернуть None.

    best_level({"simple": True, "medium": True, "strong": False}) -> "medium"
    best_level({"simple": True, "medium": False, "strong": True}) -> "simple"
    """
    # TODO: цикл for по LEVELS, внутри — if/continue/break.
    #       Многоточие ниже — заглушка из заготовки: замените его своим кодом.
    return ...


print(best_level({"simple": True, "medium": True, "strong": False}))
print(best_level({"simple": True, "medium": False, "strong": True}))
Проверь себя6 вопросов
1 Автогрейдер показал: Simple 4/4, Medium 2/3, Strong 1/1, Boss 1/1. Какой уровень засчитан?
2 В каком файле вы пишете решение ДЗ s0-hw01-python-warmup?
3 Что стоит включить в запрос к ИИ-ревьюеру?Можно выбрать несколько ответов
4 Почему задания в браузере не используют PyTorch?
5 Квиз из 5 вопросов. Сколько минимум правильных ответов нужно для зачёта (порог 60 %)?
6 Какой уровень ДЗ нужен, чтобы перейти на следующую ступень? (одно слово по-английски)
  • Проверки: квиз (≥ 60 %), задание в браузере, домашняя работа с автотестами, карточки и тетрадь ошибок с вашими ошибками.
  • ДЗ: код пишем в homework/<id>/code/, проверяем командой python homework/check.py <id>, эталон в solutions/<id>/ смотрим после попытки.
  • Уровень засчитывается, только если пройдены все тесты этого и всех нижних уровней. Для перехода дальше нужен Medium.
  • Сообщение assert — подсказка: слева ваш результат, справа ожидаемый.
  • ИИ-ревьюер полезен, когда ему запрещено решать за вас и велено задавать вопросы.

Дальше: модуль 0.2 — Python с нуля: от первой программы до функций, классов, файлов и мини-проектов. Каждая тема — с задачей в браузере.