Сначала предскажите результат и запишите объяснение. После этого запускайте код, сверяйте критерии и открывайте разбор. Данные в задачах учебные.
Как выбрать практику · Карта промахов
Решать с агентом
Скопируйте этот текст и добавьте условие выбранного задания:
Ты мой тренер по 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 и покажите строки с нулём.