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
Python: задачки и вопросы
Развёрнутое пояснение:
1. Создаётся словарь base с ключом retry, значением которого является другой словарь {limit: 3}.
2. copy.copy(base) создаёт новый внешний словарь cfg, но значение по ключу retry — это тот же вложенный словарь, а не его отдельная копия.
3. Через cfg['retry']['limit'] = 5 изменяется значение во вложенном словаре, на который ссылаются и base, и cfg.
4. Поэтому base['retry']['limit'] теперь равно 5, и print выводит 5.
Почему это важно: в реальных проектах часто клонируют конфиг, кэш или настройки. Если использовать поверхностное копирование, вложенные изменяемые структуры остаются общими, и правки в одной копии неожиданно затрагивают другую. Для полной изоляции вложенных объектов нужен copy.deepcopy.
2. copy.copy(base) создаёт новый внешний словарь cfg, но значение по ключу retry — это тот же вложенный словарь, а не его отдельная копия.
3. Через cfg['retry']['limit'] = 5 изменяется значение во вложенном словаре, на который ссылаются и base, и cfg.
4. Поэтому base['retry']['limit'] теперь равно 5, и print выводит 5.
Почему это важно: в реальных проектах часто клонируют конфиг, кэш или настройки. Если использовать поверхностное копирование, вложенные изменяемые структуры остаются общими, и правки в одной копии неожиданно затрагивают другую. Для полной изоляции вложенных объектов нужен copy.deepcopy.
✍2
Развёрнутое пояснение
a * b умножает векторы поэлементно: первый элемент с первым, второй со вторым, третий с третьим. Результат — массив [4 10 18]. a @ b делает те же умножения и складывает их в одно число: 4 + 10 + 18 = 32. Это скалярное произведение, на нём построены линейная регрессия и косинусное сходство текстов.
Если перепутать операторы, код не упадёт: обе операции корректны для векторов одинаковой длины. Ошибка проявится только неправильными числами в результате, и заметить её можно только по смыслу этих чисел.
Почему это важно
Задача проверяет, различаете ли вы две операции по смыслу: где нужно перемножить признаки поэлементно, а где — свернуть векторы в одно число.
💻 Если знаете Python, а с математикой за NumPy знакомы хуже — посмотрите курс «Математика для анализа данных» Практикума PRO. Скалярное произведение, нормы и работа с векторами в NumPy — это модуль линейной алгебры, дальше матанализ до градиентного спуска, теорвер и статистика до A/B-тестов. 1000+ задач, практика на Python в тренажёре, в конце — симулятор математической секции собеседования.
Реклама. Рекламодатель: АНО ДПО "Образовательные технологии Яндекса" ИНН 7704282033, erid: 2W5zFFzVHzh
Если перепутать операторы, код не упадёт: обе операции корректны для векторов одинаковой длины. Ошибка проявится только неправильными числами в результате, и заметить её можно только по смыслу этих чисел.
Почему это важно
Задача проверяет, различаете ли вы две операции по смыслу: где нужно перемножить признаки поэлементно, а где — свернуть векторы в одно число.
Реклама. Рекламодатель: АНО ДПО "Образовательные технологии Яндекса" ИНН 7704282033, erid: 2W5zFFzVHzh
Please open Telegram to view this post
VIEW IN TELEGRAM
✍2
Первый модуль курса «Алгоритмы и структуры данных» в Практикуме PRO открыт бесплатно: 10 тем, около 7 часов.
Внутри — понятие алгоритма, метод скользящего среднего и метод двух указателей, пробные задачи с проверкой. Хватает, чтобы ещё до оплаты понять, нужен ли вам курс целиком.
Дальше в программе: оценка сложности, структуры данных, сортировки, хеш-таблицы, деревья, графы и динамическое программирование. 100+ задач, код-ревью от практикующих разработчиков и пробное алгоритмическое собеседование. Курс идёт 4 месяца при нагрузке около 10 часов в неделю.
💻 Открыть бесплатный модуль: https://tprg.ru/n6Cr
Реклама. Рекламодатель: АНО ДПО "Образовательные технологии Яндекса" ИНН 7704282033, erid: 2W5zFKAB2eV
Внутри — понятие алгоритма, метод скользящего среднего и метод двух указателей, пробные задачи с проверкой. Хватает, чтобы ещё до оплаты понять, нужен ли вам курс целиком.
Дальше в программе: оценка сложности, структуры данных, сортировки, хеш-таблицы, деревья, графы и динамическое программирование. 100+ задач, код-ревью от практикующих разработчиков и пробное алгоритмическое собеседование. Курс идёт 4 месяца при нагрузке около 10 часов в неделю.
Реклама. Рекламодатель: АНО ДПО "Образовательные технологии Яндекса" ИНН 7704282033, erid: 2W5zFKAB2eV
Please open Telegram to view this post
VIEW IN TELEGRAM
✍3
Python: задачки и вопросы
Развёрнутое пояснение:
1. Импортируется Counter из collections.
2. a получает значения x=2, y=1; b получает x=1, y=2.
3. Операция a - b вычитает счётчики по каждому ключу: x становится 2 - 1 = 1, y становится 1 - 2 = -1.
4. Counter автоматически отбрасывает ключи с неположительным значением, поэтому результат содержит только x=1.
5. Метод elements() возвращает итератор, который выдаёт каждый ключ столько раз, чему равен его положительный счётчик: здесь 'x' один раз.
6. list(...) превращает итератор в список ['x'], который и печатает print.
Почему это важно: при сравнении частот событий, тегов или ошибок за два периода часто используют вычитание Counter. Важно помнить, что отрицательные и нулевые значения исчезают из результата, а elements() не покажет отсутствующие ключи, иначе можно принять просевшие метрики за новые события.
2. a получает значения x=2, y=1; b получает x=1, y=2.
3. Операция a - b вычитает счётчики по каждому ключу: x становится 2 - 1 = 1, y становится 1 - 2 = -1.
4. Counter автоматически отбрасывает ключи с неположительным значением, поэтому результат содержит только x=1.
5. Метод elements() возвращает итератор, который выдаёт каждый ключ столько раз, чему равен его положительный счётчик: здесь 'x' один раз.
6. list(...) превращает итератор в список ['x'], который и печатает print.
Почему это важно: при сравнении частот событий, тегов или ошибок за два периода часто используют вычитание Counter. Важно помнить, что отрицательные и нулевые значения исчезают из результата, а elements() не покажет отсутствующие ключи, иначе можно принять просевшие метрики за новые события.
✍2
Python: задачки и вопросы
Развёрнутое пояснение:
Ключ сортировки — второе число пары, поэтому ('b', 1) и ('d', 1) для сортировки равны. Python гарантирует стабильность: равные по ключу элементы сохраняют исходный порядок. 'b' стояла раньше 'd', 'a' раньше 'c' — ответ A.
Гарантия прописана в документации. Ошибаются здесь обычно те, кто привык к C++, где std::sort стабильность не обещает и для неё существует отдельный std::stable_sort.
Практическое следствие: сортировка по нескольким критериям делается последовательными проходами от младшего ключа к старшему, без составных кортежей.
Почему это важно
Задача выглядит как вопрос про синтаксис, но отвечает на неё знание алгоритма: какие гарантии даёт сортировка, почему, чем это отличается в других языках.
💻 Если хочется разобраться, как устроены сортировки, — посмотрите курс « Алгоритмы и структуры данных » Практикума PRO, там под них отведён отдельный модуль. Решения проходят автопроверку в Яндекс Контесте с ограничениями по времени и памяти, финальные задачи смотрит ревьюер и требует обосновать сложность, в конце — пробное алгоритмическое собеседование с наставником.
Реклама. Рекламодатель: АНО ДПО "Образовательные технологии Яндекса" ИНН 7704282033, erid: 2W5zFK4PUL9
Please open Telegram to view this post
VIEW IN TELEGRAM
✍3
Python: задачки и вопросы
Развёрнутое пояснение:
1. Цикл for event_type in ('click', 'scroll') начинает первую итерацию: event_type = 'click'.
2. На этой итерации определяется функция handler(e=event_type). Значение по умолчанию для e вычисляется сейчас и сохраняется как 'click' внутри объекта функции.
3. Переменная event_type в теле handler не является локальной: она свободная и будет искаться в объемлющей области видимости во время вызова.
4. Вторая итерация: event_type = 'scroll'. Определяется новая функция handler, её значение по умолчанию для e — 'scroll'.
5. После цикла event_type остаётся равной 'scroll', так как это последнее значение итерируемого.
6. Вызов handlers[0]() использует значение по умолчанию e='click' и берёт event_type='scroll' из замыкания, возвращая ('click', 'scroll').
7. Вызов handlers[1]() использует e='scroll' и event_type='scroll', возвращая ('scroll', 'scroll').
Почему это важно: разница между ранним и поздним связыванием встречается при создании колбэков, обработчиков событий и фабрик функций в цикле. Ошибка приводит к тому, что все созданные обработчики ссылаются на последнее значение переменной цикла, а использование параметра по умолчанию — распространённый способ зафиксировать нужное значение для каждой функции.
2. На этой итерации определяется функция handler(e=event_type). Значение по умолчанию для e вычисляется сейчас и сохраняется как 'click' внутри объекта функции.
3. Переменная event_type в теле handler не является локальной: она свободная и будет искаться в объемлющей области видимости во время вызова.
4. Вторая итерация: event_type = 'scroll'. Определяется новая функция handler, её значение по умолчанию для e — 'scroll'.
5. После цикла event_type остаётся равной 'scroll', так как это последнее значение итерируемого.
6. Вызов handlers[0]() использует значение по умолчанию e='click' и берёт event_type='scroll' из замыкания, возвращая ('click', 'scroll').
7. Вызов handlers[1]() использует e='scroll' и event_type='scroll', возвращая ('scroll', 'scroll').
Почему это важно: разница между ранним и поздним связыванием встречается при создании колбэков, обработчиков событий и фабрик функций в цикле. Ошибка приводит к тому, что все созданные обработчики ссылаются на последнее значение переменной цикла, а использование параметра по умолчанию — распространённый способ зафиксировать нужное значение для каждой функции.
✍1
Плохо помните математику и боитесь её, но она нужна для работы? Присмотритесь к курсу «Математика для анализа данных» от Практикума PRO.
В рамках курса вы решите 100+ практических задач и подготовитесь к математической части собеседований аналитиков и специалистов по data science. А для этого погрузитесь в четыре раздела математики:
1. Линейная алгебра: векторы, матрицы, скалярное произведение, нормы, определители, смена базиса.
2. Функции: линейные и полиномиальные, логарифмы, модули, композиции функций, графики.
3. Матанализ: пределы, производные, интегралы, функции нескольких переменных и градиентный спуск.
4. Теорвер и матстат: дискретные и непрерывные случайные величины, распределения, теорема Байеса, корреляция, центральная предельная теорему, А/B-тестирование, гипотезы, p-value.
Можно прийти даже с забытыми школьными знаниями. Формулы разбирают на понятных жизненных задачах с кодом на Python и визуализациями. Есть преподаватель математики, который отвечает на вопросы по сложным задачам, а встроенный ИИ-помощник объяснит одну и ту же формулу разными словами.
💻 Протестировать платформу и формат обучения, а также пройти первый урок бесплатно: https://tprg.ru/WcrJ
В рамках курса вы решите 100+ практических задач и подготовитесь к математической части собеседований аналитиков и специалистов по data science. А для этого погрузитесь в четыре раздела математики:
1. Линейная алгебра: векторы, матрицы, скалярное произведение, нормы, определители, смена базиса.
Зачем. Работа с данными держится на линейной алгебре. Вектор — это просто строка с характеристиками объекта (например, клиент с параметрами: возраст, доход, количество покупок). Матрица — это вся таблица с данными. Линейная алгебра учит превращать текст или картинки в наборы чисел и вычислять, насколько объекты похожи друг на друга (например, для рекомендательных систем или поисковиков).
2. Функции: линейные и полиномиальные, логарифмы, модули, композиции функций, графики.
Зачем. Показывает, как описывать реальные процессы формулами. Например, как цена квартиры зависит от её площади или как меняется спрос на товар. Логарифмы позволяют работать с огромными разбросами значений (когда у одного пользователя 2 подписчика, а у другого — 10 миллионов), приводя их к удобному масштабу.
3. Матанализ: пределы, производные, интегралы, функции нескольких переменных и градиентный спуск.
Зачем. Помогает находить наилучшие (оптимальные) решения и обучать алгоритмы. Производная показывает скорость и направление изменений. Алгоритм градиентного спуска использует производные, чтобы заставлять нейросети и модели машинного обучения совершать всё меньше ошибок. Это главный механизм «обучения» ИИ.
4. Теорвер и матстат: дискретные и непрерывные случайные величины, распределения, теорема Байеса, корреляция, центральная предельная теорему, А/B-тестирование, гипотезы, p-value.
Зачем. Учит принимать обоснованные решения в условиях неопределенности. Статистика нужна, чтобы измерять взаимосвязи через A/B-тесты (например, действительно ли новая кнопка на сайте увеличила продажи или это просто случайность) и оценить надежность выводов.
Можно прийти даже с забытыми школьными знаниями. Формулы разбирают на понятных жизненных задачах с кодом на Python и визуализациями. Есть преподаватель математики, который отвечает на вопросы по сложным задачам, а встроенный ИИ-помощник объяснит одну и ту же формулу разными словами.
Please open Telegram to view this post
VIEW IN TELEGRAM
✍1
Python: задачки и вопросы
Развёрнутое пояснение:
1. Вызов await t(1) запускает первую корутину: на печать идёт 1, затем await sleep(0) приостанавливает выполнение main и отдаёт управление event loop.
2. create_task(t(3)) только планирует фоновую задачу в очередь event loop, но не запускает её немедленно.
3. await t(2) печатает 2 и тоже приостанавливается на sleep(0).
4. Event loop видит три готовых продолжения: t(1), запланированная t(3) и main после t(2). Порядок FIFO, поэтому сначала возобновляется t(1), которая печатает 2 и завершается.
5. Следующей запускается t(3): печатается 3, затем await sleep(0) приостанавливает её.
6. Затем event loop возвращает управление в main после await t(2), где сразу выполняется await x и main ждёт завершения t(3).
7. Event loop возобновляет t(3), печатается 4, задача завершается.
8. В результате на печать идёт последовательность 122334.
Почему это важно: в асинхронном коде побочные эффекты и порядок записи строк не всегда совпадают с порядком выполнения. sleep(0) — не пустая операция, а точка уступки управления, после которой event loop выбирает следующую задачу из очереди. Это влияет на порядок логов, обновление кэша, коммитов состояния и других операций с побочными эффектами, поэтому важно понимать, что create_task планирует задачу, а await передаёт управление.
2. create_task(t(3)) только планирует фоновую задачу в очередь event loop, но не запускает её немедленно.
3. await t(2) печатает 2 и тоже приостанавливается на sleep(0).
4. Event loop видит три готовых продолжения: t(1), запланированная t(3) и main после t(2). Порядок FIFO, поэтому сначала возобновляется t(1), которая печатает 2 и завершается.
5. Следующей запускается t(3): печатается 3, затем await sleep(0) приостанавливает её.
6. Затем event loop возвращает управление в main после await t(2), где сразу выполняется await x и main ждёт завершения t(3).
7. Event loop возобновляет t(3), печатается 4, задача завершается.
8. В результате на печать идёт последовательность 122334.
Почему это важно: в асинхронном коде побочные эффекты и порядок записи строк не всегда совпадают с порядком выполнения. sleep(0) — не пустая операция, а точка уступки управления, после которой event loop выбирает следующую задачу из очереди. Это влияет на порядок логов, обновление кэша, коммитов состояния и других операций с побочными эффектами, поэтому важно понимать, что create_task планирует задачу, а await передаёт управление.
✍2
Python: задачки и вопросы
Развёрнутое пояснение:
1. В строке from decimal import Decimal импортируется тип Decimal.
2. Выражение Decimal('0.1') + Decimal('0.2') выполняет точное десятичное сложение и даёт Decimal('0.3').
3. Сравнение a == 0.3 сравнивает Decimal с float. Python преобразует float 0.3 в Decimal через его двоичное приближение, которое не равно Decimal('0.3'), поэтому результат False.
4. Сравнение a == Decimal('0.3') сравнивает два одинаковых Decimal, поэтому True.
5. float(a) превращает Decimal('0.3') в float 0.3, и сравнение с литералом 0.3 даёт True.
6. Итоговый вывод: False True True.
Почему это важно: В финансовых расчётах и работе с ценами часто используют Decimal, чтобы избежать ошибок float. Но при смешивании Decimal и float в сравнениях, сортировках или условиях результат может оказаться неожиданным: равенство может быть ложным, а max или min может выбрать не тот элемент. Правильно — приводить оба операнда к Decimal или к float явно и не полагаться на неявное преобразование.
2. Выражение Decimal('0.1') + Decimal('0.2') выполняет точное десятичное сложение и даёт Decimal('0.3').
3. Сравнение a == 0.3 сравнивает Decimal с float. Python преобразует float 0.3 в Decimal через его двоичное приближение, которое не равно Decimal('0.3'), поэтому результат False.
4. Сравнение a == Decimal('0.3') сравнивает два одинаковых Decimal, поэтому True.
5. float(a) превращает Decimal('0.3') в float 0.3, и сравнение с литералом 0.3 даёт True.
6. Итоговый вывод: False True True.
Почему это важно: В финансовых расчётах и работе с ценами часто используют Decimal, чтобы избежать ошибок float. Но при смешивании Decimal и float в сравнениях, сортировках или условиях результат может оказаться неожиданным: равенство может быть ложным, а max или min может выбрать не тот элемент. Правильно — приводить оба операнда к Decimal или к float явно и не полагаться на неявное преобразование.
✍3
Python: задачки и вопросы
Развёрнутое пояснение:
1. MRO класса D(B, C): D, B, C, A.
2. D().f() печатает D и через super().f() делегирует следующему классу в MRO — B.
3. B.f печатает B и через super().f() делегирует следующему — C, а не A, потому что super() опирается на линейный MRO объекта, а не на прямого родителя.
4. C.f печатает C и через super().f() делегирует A.
5. A.f печатает A.
6. Итоговый вывод: DBCA.
Почему это важно: понимание MRO и super() нужно при проектировании миксинов и множественного наследования, иначе можно случайно пропустить ветку или вызвать метод дважды.
2. D().f() печатает D и через super().f() делегирует следующему классу в MRO — B.
3. B.f печатает B и через super().f() делегирует следующему — C, а не A, потому что super() опирается на линейный MRO объекта, а не на прямого родителя.
4. C.f печатает C и через super().f() делегирует A.
5. A.f печатает A.
6. Итоговый вывод: DBCA.
Почему это важно: понимание MRO и super() нужно при проектировании миксинов и множественного наследования, иначе можно случайно пропустить ветку или вызвать метод дважды.
✍4
Python: задачки и вопросы
Развёрнутое пояснение:
1. При первом обращении c.ports вызывается метод ports и возвращается список [80].
2. cached_property записывает этот объект в c.__dict__ под именем ports.
3. Вызов c.ports.append(443) изменяет именно тот список, который теперь лежит в кэше.
4. При повторном обращении c.ports cached_property находит ports в __dict__ и возвращает сохранённый объект [80, 443].
5. print выводит [80, 443].
Почему это важно: cached_property удобен для дорогих вычислений, но кэшированный объект остаётся общим для всех последующих обращений. Если метод возвращает изменяемую коллекцию и кто-то её мутирует, изменения застревают в кэше. В конфигах и синглтонах это приводит к «засорению» настроек между тестами или запросами: безопаснее возвращать неизменяемые копии или отделять изменяемое состояние от кэшированного свойства.
2. cached_property записывает этот объект в c.__dict__ под именем ports.
3. Вызов c.ports.append(443) изменяет именно тот список, который теперь лежит в кэше.
4. При повторном обращении c.ports cached_property находит ports в __dict__ и возвращает сохранённый объект [80, 443].
5. print выводит [80, 443].
Почему это важно: cached_property удобен для дорогих вычислений, но кэшированный объект остаётся общим для всех последующих обращений. Если метод возвращает изменяемую коллекцию и кто-то её мутирует, изменения застревают в кэше. В конфигах и синглтонах это приводит к «засорению» настроек между тестами или запросами: безопаснее возвращать неизменяемые копии или отделять изменяемое состояние от кэшированного свойства.
✍3