Материал

Что произойдёт? Разбор поведения кода

Раздел практикиПрактика по форматам →

Сначала предскажите результат и запишите объяснение. После этого запускайте код, сверяйте критерии и открывайте разбор. Данные в задачах учебные.

Как выбрать практику · Карта промахов

Решать с агентом

Скопируйте этот текст и добавьте условие выбранного задания:

Ты мой тренер по backend. Я решаю задачу самостоятельно. Сначала попроси мой прогноз и объяснение; не запускай код и не раскрывай результат. После моего ответа проверь рассуждение, предложи один крайний случай и только затем покажи разбор. Если я прошу подсказку, давай один небольшой намёк за раз.

Прокрутите таблицу по горизонтали →

Задание Этап Ориентир
Общий список после копирования Базовый Python 15 мин
Когда генератор увидит изменение Инженерный Python 15 мин
Три кнопки и одно замыкание Инженерный Python 15 мин
Порядок ответов и порядок завершения Async и фоновые задачи 20 мин
Куда пропали залы без броней SQL и PostgreSQL 20 мин

Общий список после копирования

Не запускайте код сразу. Запишите точный вывод трёх списков и объясните, когда данные стали общими.

template = {"alerts": []}
first = template.copy()
second = template.copy()
first["alerts"].append("sms")
second["alerts"] = ["email"]
print(template["alerts"], first["alerts"], second["alerts"])

Проверка результата

  • Записаны все три списка в правильном порядке.
  • Объяснена разница между изменением вложенного списка и заменой значения ключа.
  • Показано, какие объекты имеют одинаковую идентичность до и после присваивания.
Подсказка — после своей попытки

Нарисуйте два словаря и стрелки к их значениям.

Разбор — после своей попытки

Вывод: ['sms'] ['sms'] ['email']. Поверхностные копии сначала ссылаются на один список. append меняет его, а присваивание ключу во втором словаре заменяет только одну ссылку.

Ожидаемый вывод:

['sms'] ['sms'] ['email']

Усложнение: Замените поверхностное копирование на независимое создание настроек и проверьте вложенный словарь.

Вернуться к этапу

Когда генератор увидит изменение

Предскажите обе строки вывода до запуска. В какой момент выполнится тело генератора?

events = ["queued", "ready"]
def unread():
    for value in events:
        yield value.upper()
stream = unread()
events.append("closed")
print(next(stream))
events[1] = "sent"
print(list(stream))

Проверка результата

  • Первая строка и остаток генератора приведены в точном порядке.
  • Объяснено, когда читается список и когда читается очередной элемент.
  • Проверено, что повторный list(stream) пуст.
Подсказка — после своей попытки

Посмотрите, выполняется ли цикл при вызове `unread()` или при `next(stream)`.

Разбор — после своей попытки

Сначала печатается QUEUED, затем ['SENT', 'CLOSED']. Генератор начинает обход при первом next; дальнейшие элементы он берёт из уже изменённого списка.

Ожидаемый вывод:

QUEUED
['SENT', 'CLOSED']

Усложнение: Передайте в генератор снимок списка и сравните результат.

Вернуться к этапу

Три кнопки и одно замыкание

Кнопки создаются в цикле. Что напечатает вызов всех обработчиков после завершения цикла?

buttons = []
for action in ("open", "save", "close"):
    buttons.append(lambda: action)
print([button() for button in buttons])

Проверка результата

  • Названы все три значения результата.
  • Объяснено, какая переменная захватывается замыканием.
  • Предложено исправление с отдельным значением для каждого обработчика.
Подсказка — после своей попытки

Вызовы происходят после цикла: чему в этот момент равна `action`?

Разбор — после своей попытки

Вывод: ['close', 'close', 'close']. Каждый обработчик ищет одну и ту же переменную action при вызове. Отдельный аргумент фабрики или значение по умолчанию закрепит действие.

Ожидаемый вывод:

['close', 'close', 'close']

Усложнение: Перепишите цикл через фабрику функций и через аргумент по умолчанию; сравните поведение.

Вернуться к этапу

Порядок ответов и порядок завершения

Предскажите две строки вывода. В этой задаче задержки нужны только для гарантированного порядка завершения: 20 мс и 0 мс.

import asyncio

async def main():
    finished = []
    async def fetch(name, delay):
        await asyncio.sleep(delay)
        finished.append(name)
        return name.upper()
    result = await asyncio.gather(fetch("a", 0.02), fetch("b", 0))
    print(result)
    print(finished)

asyncio.run(main())

Проверка результата

  • Результат gather и список завершений названы отдельно.
  • Объяснено, почему более быстрая задача не меняет порядок возвращаемых значений.
  • Указано, какой список изменится при перестановке аргументов gather.
Подсказка — после своей попытки

Один список собирает `gather`, второй меняется внутри корутин.

Разбор — после своей попытки

gather печатает ['A', 'B'] в порядке аргументов. finished печатает ['b', 'a'] в порядке завершения при указанных задержках.

Ожидаемый вывод:

['A', 'B']
['b', 'a']

Усложнение: Добавьте третью задачу с задержкой между 0 и 20 мс и сравните оба порядка.

Вернуться к этапу

Куда пропали залы без броней

Есть залы 1, 2, 3. В зале 1 одна подтверждённая и одна отменённая бронь, в зале 2 только отменённая, в зале 3 броней нет. Какую строку вернёт запрос и почему?

SELECT r.id, COUNT(b.id) AS booked
FROM rooms AS r
LEFT JOIN bookings AS b ON b.room_id = r.id
WHERE b.status = 'confirmed'
GROUP BY r.id
ORDER BY r.id;

Проверка результата

  • Перечислены все строки результата без выдуманного нуля для отсутствующих залов.
  • Объяснено действие WHERE после LEFT JOIN.
  • Предложено место для фильтра статуса, сохраняющее все залы.
Подсказка — после своей попытки

Что даёт сравнение с `NULL` для залов без подтверждённой брони?

Разбор — после своей попытки

Запрос вернёт только (1, 1). Для залов 2 и 3 после соединения нет строки с b.status = 'confirmed'; условие WHERE удаляет их. Условие в ON сохранит оба зала с нулевым счётчиком.

Один вариант решения:

SELECT r.id, COUNT(b.id) AS booked
FROM rooms AS r
LEFT JOIN bookings AS b
  ON b.room_id = r.id AND b.status = 'confirmed'
GROUP BY r.id
ORDER BY r.id;

Усложнение: Перенесите условие статуса в ON и покажите строки с нулём.

Вернуться к этапу