Python: задачки и вопросы
Развёрнутое пояснение:
1. Вызов save(10) входит в функцию и сразу попадает в блок try.
2. Внутри try выполняется value / 0, что порождает исключение ZeroDivisionError.
3. Управление переходит в блок except ZeroDivisionError, который готовится вернуть строку "fail".
4. Перед тем как функция завершится, Python обязательно выполняет блок finally, независимо от того, было ли исключение.
5. В finally встречается собственный return "cleanup"; return в finally заменяет любой ранее подготовленный результат или даже не обработанное исключение.
6. Поэтому save(10) возвращает "cleanup", и print выводит cleanup.
Почему это важно: return в finally перекрывает не только обычные значения, но и исключения, что легко превращает функцию очистки ресурсов в источник молчаливых потерь ошибок. В реальном коде finally стоит использовать только для освобождения ресурсов, закрытия соединений и снятия флагов, а итоговый результат или статус ошибки возвращать из try/except.
2. Внутри try выполняется value / 0, что порождает исключение ZeroDivisionError.
3. Управление переходит в блок except ZeroDivisionError, который готовится вернуть строку "fail".
4. Перед тем как функция завершится, Python обязательно выполняет блок finally, независимо от того, было ли исключение.
5. В finally встречается собственный return "cleanup"; return в finally заменяет любой ранее подготовленный результат или даже не обработанное исключение.
6. Поэтому save(10) возвращает "cleanup", и print выводит cleanup.
Почему это важно: return в finally перекрывает не только обычные значения, но и исключения, что легко превращает функцию очистки ресурсов в источник молчаливых потерь ошибок. В реальном коде finally стоит использовать только для освобождения ресурсов, закрытия соединений и снятия флагов, а итоговый результат или статус ошибки возвращать из try/except.
✍1
Python: задачки и вопросы
Развёрнутое пояснение:
1. Создаётся список orders = [120, 80, 300, 50].
2. Создаётся генератор big = (o for o in orders if o > 100). Сам он ещё ничего не читает, а только запоминает источник и условие.
3. orders.append(400) изменяет исходный список: теперь orders = [120, 80, 300, 50, 400].
4. sum(big) начинает итерировать генератор. На этом шаге он проходит по текущему списку и отбирает элементы больше 100: 120, 300, 400.
5. Сумма отобранных элементов равна 820.
Почему это важно: ленивые итераторы не делают снимок данных в момент создания, а читают источник по мере потребления. В продакшене это встречается в ETL-пайплайнах, потоковой обработке и работе с большими файлами, где данные могут меняться между созданием пайплайна и его запуском. Понимание этого помогает избежать неожиданных результатов при обработке заказов, логов или метрик.
2. Создаётся генератор big = (o for o in orders if o > 100). Сам он ещё ничего не читает, а только запоминает источник и условие.
3. orders.append(400) изменяет исходный список: теперь orders = [120, 80, 300, 50, 400].
4. sum(big) начинает итерировать генератор. На этом шаге он проходит по текущему списку и отбирает элементы больше 100: 120, 300, 400.
5. Сумма отобранных элементов равна 820.
Почему это важно: ленивые итераторы не делают снимок данных в момент создания, а читают источник по мере потребления. В продакшене это встречается в ETL-пайплайнах, потоковой обработке и работе с большими файлами, где данные могут меняться между созданием пайплайна и его запуском. Понимание этого помогает избежать неожиданных результатов при обработке заказов, логов или метрик.
✍2
Python: задачки и вопросы
Развёрнутое пояснение:
1. В классе OrderBatch определён атрибут items, который ссылается на пустой список. Это атрибут класса, а не экземпляра.
2. Создание a и b не создаёт новых списков, оба экземпляра через поиск атрибутов находят один общий список класса.
3. a.items.append(1) изменяет этот общий список, добавляя в него 1.
4. print(b.items) обращается к тому же атрибуту класса и выводит [1].
Почему это важно: class attributes часто используются как значения по умолчанию, а пустые изменяемые коллекции в них — классическая ошибка, приводящая к общему состоянию между экземплярами. Это проявляется в тестах, бэкенд-сервисах и кэшах, где экземпляры начинают неожиданно влиять друг на друга.
2. Создание a и b не создаёт новых списков, оба экземпляра через поиск атрибутов находят один общий список класса.
3. a.items.append(1) изменяет этот общий список, добавляя в него 1.
4. print(b.items) обращается к тому же атрибуту класса и выводит [1].
Почему это важно: class attributes часто используются как значения по умолчанию, а пустые изменяемые коллекции в них — классическая ошибка, приводящая к общему состоянию между экземплярами. Это проявляется в тестах, бэкенд-сервисах и кэшах, где экземпляры начинают неожиданно влиять друг на друга.
✍1
Развёрнутое пояснение
Здесь три строки кода, но логика та же, что и в распределённых кешах: ограниченная ёмкость, политика вытеснения, цена промаха.
Отличие в том, что 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), чтобы каждый ключ имел свой независимый список.
✍4
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. Правильный шаблон — добавить
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