Развёрнутое пояснение
Здесь три строки кода, но логика та же, что и в распределённых кешах: ограниченная ёмкость, политика вытеснения, цена промаха.
Отличие в том, что functools.lru_cache реализует строгий LRU, а, например, Redis с политикой allkeys-lru — приближённый: он сэмплирует несколько ключей и вытесняет наименее свежий из выборки, потому что честный LRU на миллионах ключей слишком дорог по памяти.
Почему это важно
Такие компромиссы и есть предмет архитектурных решений: что вытеснять, как инвалидировать, что случится с базой, когда тысячи запросов промахнутся одновременно.
💻 В Практикуме PRO есть курс «Архитектура программного обеспечения» , и там целый модуль посвящён кешированию, мониторингу и адаптации к нагрузке. Изучите паттерны серверного и HTTP-кеширования, стратегии инвалидации кеша, а также Backpressure и Circuit Breaker для защиты системы под нагрузкой.
Весь курс идёт 6 месяцев и включает 11 проектов на основе реальных архитектур. Входные требования высокие: вам понадобится опыт от двух лет коммерческой разработки и уверенные знания сразу в нескольких областях, чтобы понимать, что происходит.
Чтобы принять решение, можно посмотреть бесплатный модуль и пройти входное тестирование: https://tprg.ru/StFQ
Реклама. Рекламодатель: АНО ДПО "Образовательные технологии Яндекса" ИНН 7704282033, erid: 2W5zFHRceHw
Отличие в том, что functools.lru_cache реализует строгий LRU, а, например, Redis с политикой allkeys-lru — приближённый: он сэмплирует несколько ключей и вытесняет наименее свежий из выборки, потому что честный LRU на миллионах ключей слишком дорог по памяти.
Почему это важно
Такие компромиссы и есть предмет архитектурных решений: что вытеснять, как инвалидировать, что случится с базой, когда тысячи запросов промахнутся одновременно.
Весь курс идёт 6 месяцев и включает 11 проектов на основе реальных архитектур. Входные требования высокие: вам понадобится опыт от двух лет коммерческой разработки и уверенные знания сразу в нескольких областях, чтобы понимать, что происходит.
Чтобы принять решение, можно посмотреть бесплатный модуль и пройти входное тестирование:
Реклама. Рекламодатель: АНО ДПО "Образовательные технологии Яндекса" ИНН 7704282033, erid: 2W5zFHRceHw
Please open Telegram to view this post
VIEW IN TELEGRAM
✍2
Python: задачки и вопросы
Развёрнутое пояснение:
1. Вызывается dict.fromkeys с двумя аргументами: список ключей и один пустой список-значение.
2. Метод создаёт словарь, но не копирует значение для каждого ключа: api, db и cache получают ссылку на один и тот же список.
3. Обращение status['api'] возвращает этот общий список, и append('ok') изменяет его.
4. Поскольку db и cache указывают на тот же объект, их значения тоже становятся ['ok'].
5. print(status) выводит словарь, где каждый ключ содержит ['ok'].
Почему это важно: такая инициализация часто встречается при создании словарей статусов, группировок или конфигов с изменяемыми значениями. Вместо dict.fromkeys(keys, []) следует использовать dict comprehension {k: [] for k in keys} или collections.defaultdict(list), чтобы каждый ключ имел свой независимый список.
2. Метод создаёт словарь, но не копирует значение для каждого ключа: api, db и cache получают ссылку на один и тот же список.
3. Обращение status['api'] возвращает этот общий список, и append('ok') изменяет его.
4. Поскольку db и cache указывают на тот же объект, их значения тоже становятся ['ok'].
5. print(status) выводит словарь, где каждый ключ содержит ['ok'].
Почему это важно: такая инициализация часто встречается при создании словарей статусов, группировок или конфигов с изменяемыми значениями. Вместо dict.fromkeys(keys, []) следует использовать dict comprehension {k: [] for k in keys} или collections.defaultdict(list), чтобы каждый ключ имел свой независимый список.
✍5
Python: задачки и вопросы
Развёрнутое пояснение:
1. Создаётся deque с maxlen=3: это кольцевой буфер, который хранит не более трёх элементов.
2. Цикл добавляет строки от 'a' до 'e'. Первые три ('a', 'b', 'c') помещаются полностью.
3. При добавлении 'd' буфер переполнен, и самый старый элемент слева ('a') автоматически вытесняется: остаётся ['b', 'c', 'd'].
4. При добавлении 'e' аналогично вытесняется 'b': буфер равен ['c', 'd', 'e'].
5. appendleft('f') вставляет элемент слева. Вытеснение всегда происходит с противоположного от вставки конца, поэтому удаляется самый правый элемент 'e'.
6. Итоговый буфер: ['f', 'c', 'd'], его и печатает print(list(recent)).
Почему это важно: deque с maxlen — стандартный способ хранить последние N записей без ручной обрезки: буфер свежих строк лога, история последних запросов, скользящее окно метрик. Ошибка в понимании того, с какого конца вытесняются элементы, приводит к тому, что в алертах и отладке теряются именно те записи, которые нужны: например, appendleft удобен для «самое свежее слева», но молча выбрасывает элементы справа, и если код дальше читает буфер с другого конца, данные «исчезают» без какого-либо исключения.
2. Цикл добавляет строки от 'a' до 'e'. Первые три ('a', 'b', 'c') помещаются полностью.
3. При добавлении 'd' буфер переполнен, и самый старый элемент слева ('a') автоматически вытесняется: остаётся ['b', 'c', 'd'].
4. При добавлении 'e' аналогично вытесняется 'b': буфер равен ['c', 'd', 'e'].
5. appendleft('f') вставляет элемент слева. Вытеснение всегда происходит с противоположного от вставки конца, поэтому удаляется самый правый элемент 'e'.
6. Итоговый буфер: ['f', 'c', 'd'], его и печатает print(list(recent)).
Почему это важно: deque с maxlen — стандартный способ хранить последние N записей без ручной обрезки: буфер свежих строк лога, история последних запросов, скользящее окно метрик. Ошибка в понимании того, с какого конца вытесняются элементы, приводит к тому, что в алертах и отладке теряются именно те записи, которые нужны: например, appendleft удобен для «самое свежее слева», но молча выбрасывает элементы справа, и если код дальше читает буфер с другого конца, данные «исчезают» без какого-либо исключения.
✍2
Python: задачки и вопросы
Развёрнутое пояснение:
1. partial(collect, []) создаёт объект add, в котором аргумент acc уже связан с одним объектом списка [].
2. print(add(1), add(2)) сначала вычисляет оба аргумента.
3. add(1) вызывает collect([], 1): в список добавляется 1, возвращается тот же список [1].
4. add(2) вызывает collect([1], 2): в этот же список добавляется 2, возвращается уже [1, 2].
5. print получает две ссылки на один и тот же список [1, 2] и печатает "[1, 2] [1, 2]".
Почему это важно: functools.partial часто используют для фиксации аргументов, но если такой аргумент изменяемый, все вызовы делят общее состояние. Это стреляет в фабриках обработчиков, логгерах и callback'ах, где частично применённый изменяемый объект оказывается общим для разных вызовов.
2. print(add(1), add(2)) сначала вычисляет оба аргумента.
3. add(1) вызывает collect([], 1): в список добавляется 1, возвращается тот же список [1].
4. add(2) вызывает collect([1], 2): в этот же список добавляется 2, возвращается уже [1, 2].
5. print получает две ссылки на один и тот же список [1, 2] и печатает "[1, 2] [1, 2]".
Почему это важно: functools.partial часто используют для фиксации аргументов, но если такой аргумент изменяемый, все вызовы делят общее состояние. Это стреляет в фабриках обработчиков, логгерах и callback'ах, где частично применённый изменяемый объект оказывается общим для разных вызовов.
✍1
Python: задачки и вопросы
Развёрнутое пояснение:
1. Создаётся список levels с тремя строками.
2. dict.fromkeys(levels, []) строит словарь, в котором каждый ключ получает не свой список, а одну и ту же ссылку на один пустой список.
3. Обращение buckets['warn'] возвращает тот же общий список.
4. Метод append('disk full') изменяет этот единственный список.
5. print(buckets) выводит словарь, где под всеми тремя ключами лежит уже изменённый список.
Почему это важно: при группировке событий, логов или метрик по категориям часто пишут buckets = dict.fromkeys(groups, []), ожидая независимые корзины. В результате запись в одну категорию молча заражает все остальные, что даёт трудноуловимый баг в аналитике и обработке потоков данных.
2. dict.fromkeys(levels, []) строит словарь, в котором каждый ключ получает не свой список, а одну и ту же ссылку на один пустой список.
3. Обращение buckets['warn'] возвращает тот же общий список.
4. Метод append('disk full') изменяет этот единственный список.
5. print(buckets) выводит словарь, где под всеми тремя ключами лежит уже изменённый список.
Почему это важно: при группировке событий, логов или метрик по категориям часто пишут buckets = dict.fromkeys(groups, []), ожидая независимые корзины. В результате запись в одну категорию молча заражает все остальные, что даёт трудноуловимый баг в аналитике и обработке потоков данных.
✍1
Python: задачки и вопросы
Развёрнутое пояснение:
1. retry(2) вызывается и возвращает функцию decorator.
2. Синтаксис @retry (2) применяет decorator к функции fetch.
3. decorator создаёт функцию wrapper и возвращает её.
4. Итоговое имя fetch теперь ссылается на объект wrapper.
5. Внутренняя функция wrapper не обёрнута в functools.wraps(f), поэтому её атрибут __name__ остаётся 'wrapper'.
6. print(fetch.__name__) печатает wrapper.
Почему это важно: в production-коде декораторы с аргументами часто забывают обернуть внутреннюю функцию в functools.wraps. Это ломает логирование, метрики и отладку, потому что все обёрнутые функции начинают называться wrapper, а их __doc__ становится None. Правильный шаблон — добавить @wraps (f) над wrapper.
2. Синтаксис
3. decorator создаёт функцию wrapper и возвращает её.
4. Итоговое имя fetch теперь ссылается на объект wrapper.
5. Внутренняя функция wrapper не обёрнута в functools.wraps(f), поэтому её атрибут __name__ остаётся 'wrapper'.
6. print(fetch.__name__) печатает wrapper.
Почему это важно: в production-коде декораторы с аргументами часто забывают обернуть внутреннюю функцию в functools.wraps. Это ломает логирование, метрики и отладку, потому что все обёрнутые функции начинают называться wrapper, а их __doc__ становится None. Правильный шаблон — добавить
✍1
Python: задачки и вопросы
Развёрнутое пояснение:
1. Создаётся deque с maxlen=3, то есть буфер фиксированного размера.\n2. В цикле append добавляются элементы a, b, c, d. Когда добавляется d, deque уже заполнен тремя элементами a, b, c, поэтому самый старый элемент a удаляется слева. После цикла внутри buf лежат b, c, d.\n3. Вызов buf.rotate(1) сдвигает элементы вправо на одну позицию: последний элемент d перемещается в начало, а b и c сдвигаются вправо. Порядок становится d, b, c.\n4. print(''.join(buf)) объединяет элементы в строку dbc.\n\nПочему это важно: deque с maxlen часто используется для скользящего окна последних событий, например при сборе последних логов или метрик. rotate нужен для циклических очередей и round-robin. Важно помнить, что rotate(1) двигает элементы вправо, rotate(-1) — влево, а maxlen автоматически вытесняет старые элементы с противоположного конца от добавления.
✍2
Python: задачки и вопросы
Развёрнутое пояснение:
1. Определяются две функции-генератора: inner и outer.
2. При вызове list(outer()) создаётся генератор outer.
3. Первый yield 1 в outer приостанавливает выполнение и возвращает значение 1.
4. Затем выполняется выражение v = yield from inner().
5. yield from делегирует управление генератору inner, поэтому следующим возвращается его yield 2.
6. Когда inner завершается оператором return 'done', это значение становится результатом выражения yield from.
7. Переменная v получает строку 'done'.
8. Последний yield v возвращает 'done'.
9. Генератор outer завершается, и list собирает все полученные значения: 1, 2 и 'done'.
10. print выводит [1, 2, 'done'].
Почему это важно: механика yield from используется при построении ленивых пайплайнов обработки данных, рекурсивных обходов деревьев и корутин. Понимание того, что return подгенератора становится возвращаемым значением yield from, помогает правильно комбинировать генераторы и передавать результаты между этапами пайплайна без промежуточных коллекций.
2. При вызове list(outer()) создаётся генератор outer.
3. Первый yield 1 в outer приостанавливает выполнение и возвращает значение 1.
4. Затем выполняется выражение v = yield from inner().
5. yield from делегирует управление генератору inner, поэтому следующим возвращается его yield 2.
6. Когда inner завершается оператором return 'done', это значение становится результатом выражения yield from.
7. Переменная v получает строку 'done'.
8. Последний yield v возвращает 'done'.
9. Генератор outer завершается, и list собирает все полученные значения: 1, 2 и 'done'.
10. print выводит [1, 2, 'done'].
Почему это важно: механика yield from используется при построении ленивых пайплайнов обработки данных, рекурсивных обходов деревьев и корутин. Понимание того, что return подгенератора становится возвращаемым значением yield from, помогает правильно комбинировать генераторы и передавать результаты между этапами пайплайна без промежуточных коллекций.
✍3
Python: задачки и вопросы
Развёрнутое пояснение:
1. Вызов save(5) создаёт пустой список result.
2. В try-блоке вычисляется value // 2, то есть 2, и это значение добавляется в result.
3. Достигается инструкция return result.
4. Перед фактическим возвратом управление обязательно переходит в finally-блок, так как finally выполняется при любом выходе из try/except.
5. В finally вычисляется value * 2, то есть 10, и это значение тоже добавляется в тот же список result.
6. Теперь result содержит [2, 10].
7. Функция возвращает ссылку на этот список.
8. print выводит [2, 10].
Почему это важно: в реальном коде finally используется для cleanup, метрик или освобождения ресурсов. Если при этом мутировать объект, который планируется вернуть из функции, финальное значение изменится ещё до передачи вызывающей стороне. Это стреляет в сценариях, когда return и cleanup работают с одной структурой данных, например при сборке ответа, логировании или обновлении кэша.
2. В try-блоке вычисляется value // 2, то есть 2, и это значение добавляется в result.
3. Достигается инструкция return result.
4. Перед фактическим возвратом управление обязательно переходит в finally-блок, так как finally выполняется при любом выходе из try/except.
5. В finally вычисляется value * 2, то есть 10, и это значение тоже добавляется в тот же список result.
6. Теперь result содержит [2, 10].
7. Функция возвращает ссылку на этот список.
8. print выводит [2, 10].
Почему это важно: в реальном коде finally используется для cleanup, метрик или освобождения ресурсов. Если при этом мутировать объект, который планируется вернуть из функции, финальное значение изменится ещё до передачи вызывающей стороне. Это стреляет в сценариях, когда return и cleanup работают с одной структурой данных, например при сборке ответа, логировании или обновлении кэша.
✍2
Python: задачки и вопросы
Развёрнутое пояснение:
1. Цикл for n in 'ba' регистрирует колбэки в порядке 'b', затем 'a'.
2. Вызов stack.close() запускает колбэки в обратном порядке: сначала 'a', потом 'b'.
3. Колбэк 'a' печатает a и выбрасывает RuntimeError('a'), но ExitStack ловит это исключение и продолжает выполнение оставшихся колбэков.
4. Колбэк 'b' печатает b и выбрасывает RuntimeError('b'); ExitStack запоминает уже это последнее исключение.
5. После завершения всех колбэков ExitStack поднимает сохранённое исключение, которое ловит внешний except и печатает caught b. Итоговый вывод: a, b, caught b.
Почему это важно: в реальных сервисах cleanup-колбэки закрывают соединения с базой, временные файлы или снимают блокировки. Порядок закрытия обратен порядку регистрации, при этом один упавший колбэк не должен прервать остальные освобождения ресурсов, иначе останутся висячие соединения или утечки.
2. Вызов stack.close() запускает колбэки в обратном порядке: сначала 'a', потом 'b'.
3. Колбэк 'a' печатает a и выбрасывает RuntimeError('a'), но ExitStack ловит это исключение и продолжает выполнение оставшихся колбэков.
4. Колбэк 'b' печатает b и выбрасывает RuntimeError('b'); ExitStack запоминает уже это последнее исключение.
5. После завершения всех колбэков ExitStack поднимает сохранённое исключение, которое ловит внешний except и печатает caught b. Итоговый вывод: a, b, caught b.
Почему это важно: в реальных сервисах cleanup-колбэки закрывают соединения с базой, временные файлы или снимают блокировки. Порядок закрытия обратен порядку регистрации, при этом один упавший колбэк не должен прервать остальные освобождения ресурсов, иначе останутся висячие соединения или утечки.
✍1