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
Python: задачки и вопросы
Развёрнутое пояснение:
1. State — перечисление, член State.OK имеет значение ok, но сам является отдельным объектом Enum.
2. Функция check сравнивает строку ok с объектом State.OK оператором равенства.
3. У Enum операция равенства считает равными только члены одного и того же перечисления, поэтому сравнение со строкой возвращает False.
4. В результате print выводит False.
Почему это важно: при рефакторинге строковых констант в Enum часто забывают, что сравнение со строкой из JSON или БД перестаёт работать. Правильно сравнивать через State(raw) == State.OK или raw == State.OK.value. Это типичная ошибка в валидации API и конфигурации.
2. Функция check сравнивает строку ok с объектом State.OK оператором равенства.
3. У Enum операция равенства считает равными только члены одного и того же перечисления, поэтому сравнение со строкой возвращает False.
4. В результате print выводит False.
Почему это важно: при рефакторинге строковых констант в Enum часто забывают, что сравнение со строкой из JSON или БД перестаёт работать. Правильно сравнивать через State(raw) == State.OK или raw == State.OK.value. Это типичная ошибка в валидации API и конфигурации.
✍3
Python: задачки и вопросы
Развёрнутое пояснение:
1. Создаётся пустой список queue.
2. defaultdict инициализируется фабрикой lambda: queue, которая при каждом обращении по отсутствующему ключу возвращает ссылку на этот же список.
3. tasks['api'] вызывает фабрику, возвращает queue и добавляет в него строку retry.
4. tasks['queue'] снова вызывает фабрику и возвращает тот же список queue, который уже содержит retry.
5. Поэтому print выводит ['retry'].
Почему это важно: такая ошибка возникает, когда разработчик хочет инициализировать значение изменяемым объектом и вместо конструктора list передаёт lambda, замкнутую на заранее созданный объект. В планировщиках задач, группировке событий по категориям или кэшировании результатов по ключам это приводит к тому, что разные ключи делят одно состояние. Правильный способ — передать list или функцию, создающую новый объект при каждом вызове, например defaultdict(list).
2. defaultdict инициализируется фабрикой lambda: queue, которая при каждом обращении по отсутствующему ключу возвращает ссылку на этот же список.
3. tasks['api'] вызывает фабрику, возвращает queue и добавляет в него строку retry.
4. tasks['queue'] снова вызывает фабрику и возвращает тот же список queue, который уже содержит retry.
5. Поэтому print выводит ['retry'].
Почему это важно: такая ошибка возникает, когда разработчик хочет инициализировать значение изменяемым объектом и вместо конструктора list передаёт lambda, замкнутую на заранее созданный объект. В планировщиках задач, группировке событий по категориям или кэшировании результатов по ключам это приводит к тому, что разные ключи делят одно состояние. Правильный способ — передать list или функцию, создающую новый объект при каждом вызове, например defaultdict(list).
✍2
Python: задачки и вопросы
Развёрнутое пояснение:
1. Создаётся словарь tags с ключами api и db.
2. Запускается цикл for key in tags, который получает итератор ключей словаря.
3. Итератор запоминает ожидаемое количество элементов на момент начала обхода.
4. При первом же ключе api условие срабатывает и вызывается tags.pop(key), удаляя элемент из словаря.
5. Словарь изменил размер во время итерации, поэтому при следующей попытке получить следующий ключ интерпретатор обнаруживает рассогласование и возбуждает RuntimeError: dictionary changed size during iteration.
6. print(tags) не выполняется, потому что исключение прерывает выполнение.
Почему это важно: такая ошибка часто возникает при фильтрации или очистке словарей состояния, кэша или метрик. Правильный способ — собрать ключи для удаления в отдельный список и удалять после цикла, либо создавать новый словарь через dict comprehension. Это позволяет избежать неожиданного падения на проде при обработке логов, конфигов или счётчиков.
2. Запускается цикл for key in tags, который получает итератор ключей словаря.
3. Итератор запоминает ожидаемое количество элементов на момент начала обхода.
4. При первом же ключе api условие срабатывает и вызывается tags.pop(key), удаляя элемент из словаря.
5. Словарь изменил размер во время итерации, поэтому при следующей попытке получить следующий ключ интерпретатор обнаруживает рассогласование и возбуждает RuntimeError: dictionary changed size during iteration.
6. print(tags) не выполняется, потому что исключение прерывает выполнение.
Почему это важно: такая ошибка часто возникает при фильтрации или очистке словарей состояния, кэша или метрик. Правильный способ — собрать ключи для удаления в отдельный список и удалять после цикла, либо создавать новый словарь через dict comprehension. Это позволяет избежать неожиданного падения на проде при обработке логов, конфигов или счётчиков.
✍1
Python: задачки и вопросы
Развёрнутое пояснение:
1. Список logs содержит четыре кортежа: две записи api и две db, чередующиеся. 2. itertools.groupby не сортирует данные и не ищет все одинаковые ключи во всём списке: он формирует новую группу каждый раз, когда ключ очередного элемента отличается от ключа предыдущего. 3. Последовательность ключей в logs: api, db, api, db. Поэтому группы получаются такими: api с одной записью, db с одной записью, снова api с одной записью и снова db с одной записью. 4. В dict comprehension каждый ключ перезаписывается при повторном вхождении. Сначала 'api' получает список из одной ошибки, потом 'db' — один успех, затем 'api' зативается второй группой из одной успешной записи, и в конце 'db' перезаписывается группой с одной ошибкой. 5. В итоге groups['api'] — это список из одного кортежа [('api', 'ok')], а len от него равен 1.
Почему это важно: в аналитике логов и отчётах часто ожидают, что groupby сгруппирует все записи по ключу, как SQL GROUP BY, но в Python он работает только над отсортированными или уже упорядоченными данными. Если пропустить сортировку, группы дробятся, а при записи в словарь последняя группа затирает предыдущие с тем же ключом. Это приводит к тихой потере данных и неверным агрегатам.
Почему это важно: в аналитике логов и отчётах часто ожидают, что groupby сгруппирует все записи по ключу, как SQL GROUP BY, но в Python он работает только над отсортированными или уже упорядоченными данными. Если пропустить сортировку, группы дробятся, а при записи в словарь последняя группа затирает предыдущие с тем же ключом. Это приводит к тихой потере данных и неверным агрегатам.
✍2
Приглашаем Go- и Python-разработчиков, аналитиков и продакт-менеджеров на тревел-тех хакатон от Островка.
Вам предстоит решить одну из двух AI-задач в сфере тревел-теха и побороться за главный приз — 1 000 000 ₽.
Присоединиться к хакатону можно из любой точки мира. Участвуйте командой до пяти человек и выбирайте один из двух треков:
Скорее присоединяйтесь по ссылке!
Please open Telegram to view this post
VIEW IN TELEGRAM