Развёрнутое пояснение:
1. Создаём множество с тремя объектами: 1 (int), 1.0 (float), True (bool).
2. Python сначала сравнивает хэши: hash(1), hash(1.0) и hash(True) равны.
3. При совпадении хэшей множество проверяет равенство через ==: 1 == 1.0 == True возвращает True.
4. Все три значения признаются дубликатами, и в множестве остаётся только первый вставленный элемент — 1.
5. print(len(data)) выводит 1.
Почему это важно
В ETL-пайплайнах такое поведение приводит к потере строк: дедупликация ключей int/float/bool или группировка по ним сворачивает разные сущности в одну. Поэтому в ключах явно приводят типы и не смешивают bool с числами.
💻 ETL-автоматизация — это только один из этапов разработки архитектуры данных. Если хотите системно разобраться в этой сфере, посмотрите программу Инженер данных от Практикума PRO. Вы сделаете 8 проектов, в рамках которых научитесь обрабатывать данные разными инструментами, проектировать пайплайны, создавать витрины и хранилища. Обратите внимание, что курс на полгода и он не для начинающих: вам понадобятся уверенные знания Python и SQL.
Реклама. Рекламодатель: АНО ДПО "Образовательные технологии Яндекса" ИНН 7704282033, erid: 2W5zFJBoozq
2. Python сначала сравнивает хэши: hash(1), hash(1.0) и hash(True) равны.
3. При совпадении хэшей множество проверяет равенство через ==: 1 == 1.0 == True возвращает True.
4. Все три значения признаются дубликатами, и в множестве остаётся только первый вставленный элемент — 1.
5. print(len(data)) выводит 1.
Почему это важно
В ETL-пайплайнах такое поведение приводит к потере строк: дедупликация ключей int/float/bool или группировка по ним сворачивает разные сущности в одну. Поэтому в ключах явно приводят типы и не смешивают bool с числами.
💻 ETL-автоматизация — это только один из этапов разработки архитектуры данных. Если хотите системно разобраться в этой сфере, посмотрите
Реклама. Рекламодатель: АНО ДПО "Образовательные технологии Яндекса" ИНН 7704282033, erid: 2W5zFJBoozq
✍2
Python: задачки и вопросы
Развёрнутое пояснение:
1. В main сначала вызывается create_task(f(2)), поэтому задача для f(2) встаёт в очередь цикла событий первой.
2. Затем вызывается create_task(f(1)), и задача для f(1) становится второй в очереди.
3. await t1 отдаёт управление циклу событий, который начинает выполнять отложенные задачи в порядке их постановки.
4. Сначала запускается f(2): она печатает 2, затем await asyncio.sleep(0) приостанавливает её.
5. Пока f(2) ждёт, цикл переключается на f(1), печатает 1 и тоже засыпает на 0.
6. Когда sleep(0) завершается, задачи просыпаются в том же порядке: f(2) печатает 12, затем f(1) печатает 11.
7. await t1 и await t2 завершаются, так как обе задачи уже выполнены. В stdout оказывается 2, 1, 12, 11.
Почему это важно: в асинхронных сервисах create_task лишь планирует задачу, а не запускает её мгновенно. Порядок выполнения определяется очередью событий, поэтому await в конце не меняет порядок, заданный при создании задач. Это помогает избежать неожиданного порядка вызовов при инициализации ресурсов, логировании или конкурентной обработке событий.
2. Затем вызывается create_task(f(1)), и задача для f(1) становится второй в очереди.
3. await t1 отдаёт управление циклу событий, который начинает выполнять отложенные задачи в порядке их постановки.
4. Сначала запускается f(2): она печатает 2, затем await asyncio.sleep(0) приостанавливает её.
5. Пока f(2) ждёт, цикл переключается на f(1), печатает 1 и тоже засыпает на 0.
6. Когда sleep(0) завершается, задачи просыпаются в том же порядке: f(2) печатает 12, затем f(1) печатает 11.
7. await t1 и await t2 завершаются, так как обе задачи уже выполнены. В stdout оказывается 2, 1, 12, 11.
Почему это важно: в асинхронных сервисах create_task лишь планирует задачу, а не запускает её мгновенно. Порядок выполнения определяется очередью событий, поэтому await в конце не меняет порядок, заданный при создании задач. Это помогает избежать неожиданного порядка вызовов при инициализации ресурсов, логировании или конкурентной обработке событий.
✍4
Python: задачки и вопросы
Развёрнутое пояснение:
1. Вызов fetch() входит в блок try. 2. В try возникает исключение ValueError('fail'). 3. Перед завершением функции выполняется блок finally. 4. В finally встречается return 'ok', который подавляет исключение и возвращает это значение. 5. print(fetch()) печатает ok.
Почему это важно: return, break или continue в finally могут скрыть ошибки, поэтому cleanup-код не должен возвращать значений или прерывать поток управления.
Почему это важно: return, break или continue в finally могут скрыть ошибки, поэтому cleanup-код не должен возвращать значений или прерывать поток управления.
✍5
Напишите бэкенд онлайн-кинотеатра из 10+ микросервисов за 6 месяцев:
🤯 админка и ETL из PostgreSQL в Elasticsearch (Django, Docker, nginx);
🤯 асинхронный API поиска по базе фильмов (AsyncIO, FastAPI, Redis);
🤯 сервис авторизации: OAuth 2.0, OpenID, JWT;
🤯 UGC под большой поток событий: Kafka, ClickHouse, ELK, CI на GitHub Actions;
🤯 нотификации через RabbitMQ.
Это программа курса «Мидл Python-разработчик» от Практикума PRO. Каждый модуль — отдельный сервис с новым стеком. Плюс Kubernetes: развернёте кластер и повесите Prometheus с Grafana на свои сервисы.
Сам Python тут почти не преподают: предполагается, что язык вы знаете. Учат тому, чего не даёт маленький или узкоспециализированный проект: архитектура, отказоустойчивость, OLTP/OLAP, масштабирование.
Итоговый проект — это онлайн-кинотеатр, доработанный сервисом на выбор (например, голосовым помощником), с защитой перед архитектурным комитетом.
Кому подойдёт: у вас есть больше года коммерческой разработки на Python, знакомы с SQL и Linux; вы готовы к нагрузке в 15–20 часов в неделю (выпускники называют это второй работой).
💻 Проверьте свой уровень на входном тестировании: https://tprg.ru/QGe1
Реклама. Рекламодатель: АНО ДПО "Образовательные технологии Яндекса" ИНН 7704282033, erid: 2W5zFH6Ppp1
Это программа курса «Мидл Python-разработчик» от Практикума PRO. Каждый модуль — отдельный сервис с новым стеком. Плюс Kubernetes: развернёте кластер и повесите Prometheus с Grafana на свои сервисы.
Сам Python тут почти не преподают: предполагается, что язык вы знаете. Учат тому, чего не даёт маленький или узкоспециализированный проект: архитектура, отказоустойчивость, OLTP/OLAP, масштабирование.
Итоговый проект — это онлайн-кинотеатр, доработанный сервисом на выбор (например, голосовым помощником), с защитой перед архитектурным комитетом.
Кому подойдёт: у вас есть больше года коммерческой разработки на Python, знакомы с SQL и Linux; вы готовы к нагрузке в 15–20 часов в неделю (выпускники называют это второй работой).
Реклама. Рекламодатель: АНО ДПО "Образовательные технологии Яндекса" ИНН 7704282033, erid: 2W5zFH6Ppp1
Please open Telegram to view this post
VIEW IN TELEGRAM
✍2
Python: задачки и вопросы
Развёрнутое пояснение:
1. Создаётся кортеж route = ('start',).
2. Вызов tag(route) передаёт ссылку на этот кортеж в параметр path.
3. Внутри tag выполняется path += ('end',): так как tuple неизменяемый, Python не может расширить существующий объект, а создаёт новый кортеж ('start', 'end') и присваивает его локальной переменной path.
4. Переменная route в вызывающем коде продолжает ссылаться на исходный кортеж ('start',).
5. print(route) выводит ('start',).
Почему это важно: с неизменяемыми типами (tuple, str, int) оператор += никогда не меняет исходный объект, он лишь создаёт новое значение и переприсваивает локальную ссылку. Поэтому функция, которая должна «дополнить» переданный неизменяемый объект, должна явно вернуть результат, иначе вызывающий код не увидит изменений. Это частая ошибка при работе с путями, идентификаторами и конфигурационными ключами.
2. Вызов tag(route) передаёт ссылку на этот кортеж в параметр path.
3. Внутри tag выполняется path += ('end',): так как tuple неизменяемый, Python не может расширить существующий объект, а создаёт новый кортеж ('start', 'end') и присваивает его локальной переменной path.
4. Переменная route в вызывающем коде продолжает ссылаться на исходный кортеж ('start',).
5. print(route) выводит ('start',).
Почему это важно: с неизменяемыми типами (tuple, str, int) оператор += никогда не меняет исходный объект, он лишь создаёт новое значение и переприсваивает локальную ссылку. Поэтому функция, которая должна «дополнить» переданный неизменяемый объект, должна явно вернуть результат, иначе вызывающий код не увидит изменений. Это частая ошибка при работе с путями, идентификаторами и конфигурационными ключами.
✍1
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.
✍2
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