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
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: задачки и вопросы
Развёрнутое пояснение:
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
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