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), чтобы каждый ключ имел свой независимый список.
✍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