Python: задачки и вопросы
7.52K subscribers
1.28K photos
1 video
1 file
118 links
Вопросы и задачки для подготовки к собеседованиям и прокачки навыков

Разместить рекламу: @tproger_sales_bot

Правила общения: https://tprg.ru/rules

Другие каналы: @tproger_channels

Другие наши проекты: https://tprg.ru/media
Download Telegram
Что выведет код?
Anonymous Quiz
45%
1 10
21%
1 5
19%
1 None
15%
TypeError
Метод 𝚜𝚎𝚗𝚍() отправляет значение в генератор, которое становится результатом текущего выражения 𝚢𝚒𝚎𝚕𝚍. Первый 𝚗𝚎𝚡𝚝(𝚐) запускает генератор и выдаёт 1, затем 𝚐.𝚜𝚎𝚗𝚍(5) отправляет 5 в переменную 𝚡, вычисляет 𝚡 * 2 = 10 и выдаёт это значение.​

Подробный разбор по шагам

1️⃣Создаётся объект генератора 𝚐 = 𝚐𝚎𝚗(). На этом этапе функция ещё не начала выполняться, генератор находится в состоянии «не запущен».​

2️⃣Вызов 𝚗𝚎𝚡𝚝(𝚐) запускает выполнение генератора. Код доходит до строки 𝚡 = 𝚢𝚒𝚎𝚕𝚍 1, выдаёт значение 1 и приостанавливается. Выражение 𝚢𝚒𝚎𝚕𝚍 1 ещё не завершено — переменная 𝚡 пока не получила значение. Функция 𝚙𝚛𝚒𝚗𝚝 выводит первую строку: 1.​

3️⃣Вызов 𝚐.𝚜𝚎𝚗𝚍(5) возобновляет выполнение генератора и отправляет значение 5 внутрь генератора. Это значение становится результатом выражения 𝚢𝚒𝚎𝚕𝚍 1, то есть переменная 𝚡 получает значение 5.​

4️⃣Генератор продолжает выполнение со следующей строки 𝚢𝚒𝚎𝚕𝚍 𝚡 * 2. Вычисляется 5 * 2 = 10, и это значение выдаётся наружу. Метод 𝚜𝚎𝚗𝚍() возвращает выданное значение 10, которое передаётся в 𝚙𝚛𝚒𝚗𝚝.​

5️⃣Функция 𝚙𝚛𝚒𝚗𝚝 выводит вторую строку: 10. Итого на экране появляются две строки: 1 и 10.

Эта задача показывает, что генераторы поддерживают двустороннюю коммуникацию — паттерн, используемый в конвейерах обработки данных и state machines.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
1️⃣Определены функции 𝚏𝚎𝚝𝚌𝚑 и 𝚖𝚊𝚒𝚗, но они ничего не делают сами по себе, пока не будут запущены через await/цикл событий.

2️⃣Строка 𝚌𝚘𝚛𝚘 = 𝚖𝚊𝚒𝚗() создаёт объект‑корутину 𝚌𝚘𝚛𝚘; тело 𝚖𝚊𝚒𝚗 (и, следовательно, 𝚏𝚎𝚌𝚝𝚌𝚑) при этом не исполняется.​

3️⃣Вызов 𝚌𝚘𝚛𝚘.𝚌𝚕𝚘𝚜𝚎() корректно завершает корутину, что предотвращает предупреждение «coroutine was never awaited»; метод 𝚌𝚕𝚘𝚜𝚎 доступен у корутин по спецификации.​

4️⃣Поскольку ни 𝚖𝚊𝚒𝚗, ни 𝚏𝚎𝚌𝚑 не выполнялись, строки "fetching" и "done" не печатаются; затем исполняется обычный синхронный 𝚙𝚛𝚒𝚗𝚝("after") и выводит единственную строку.

Как работает async/await
Асинхронное программирование позволяет программе не простаивать во время долгих операций (сеть, диск, база данных). Ключевое слово 𝚊𝚜𝚢𝚗𝚌 превращает функцию в корутину — объект, который можно «приостановить» на await и потом продолжить, при этом цикл событий в момент ожидания переключается на другие задачи. Для реального запуска корутины нужен либо 𝚊𝚠𝚊𝚒𝚝 внутри другой async‑функции, либо явный вызов через 𝚊𝚜𝚢𝚗𝚌𝚒𝚘.𝚛𝚞𝚗() или цикл событий.​

Подробнее
про основы асинхронности в Python и чуть глубже о том, почему мало кто на самом деле понимает как это работает.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4🔥2
Разбор по шагам

1️⃣Декоратор @𝚙𝚛𝚘𝚙𝚎𝚛𝚝𝚢 создаёт объект дескриптора, который хранит функцию 𝚗𝚊𝚖𝚎. Этот объект дескриптор помещается в словарь класса 𝚄𝚜𝚎𝚛.𝚍𝚒𝚌𝚝 под именем 𝚗𝚊𝚖𝚎.

2️⃣Выражение 𝚄𝚜𝚎𝚛.𝚗𝚊𝚖𝚎 — это обращение к атрибуту через класс (без создания экземпляра). Python находит дескриптор, но поскольку обращение идёт не через экземпляр, дескриптор просто возвращает сам себя — объект 𝚙𝚛𝚘𝚙𝚎𝚛𝚝𝚢. Первый 𝚙𝚛𝚒𝚗𝚝 выводит что‑то вроде <𝚙𝚛𝚘𝚙𝚎𝚛𝚝𝚢 𝚘𝚋𝚓𝚎𝚌𝚝 𝚊𝚝 𝟶𝚡...>.​

3️⃣Выражение 𝚄𝚜𝚎𝚛() создаёт новый экземпляр класса 𝚄𝚜𝚎𝚛.

4️⃣Обращение .𝚗𝚊𝚖𝚎 на этом экземпляре заставляет дескриптор сработать: он вызывает сохранённую функцию 𝚗𝚊𝚖𝚎(𝚜𝚎𝚕𝚏) и возвращает её результат «𝚒𝚗𝚜𝚝𝚊𝚗𝚌𝚎».​

5️⃣Второй 𝚙𝚛𝚒𝚗𝚝 выводит строку «𝚒𝚗𝚜𝚝𝚊𝚗𝚌𝚎».

Когда интерпретатор видит метод с декоратором 𝚙𝚛𝚘𝚙𝚎𝚛𝚝𝚢, он примерно делает так:​
🔘берёт функцию 𝚏𝚞𝚗𝚌 = 𝚄𝚜𝚎𝚛.𝚗𝚊𝚖𝚎;
🔘создаёт объект 𝚙𝚛𝚘𝚙 = 𝚙𝚛𝚘𝚙𝚎𝚛𝚝𝚢(𝚏𝚞𝚗𝚌);
🔘кладёт его в класс: в 𝚄𝚜𝚎𝚛.𝚍𝚒𝚌𝚝 под ключом 𝚗𝚊𝚖𝚎 теперь лежит не функция, а объект 𝚙𝚛𝚘𝚙𝚎𝚛𝚝𝚢.​

Класс 𝚙𝚛𝚘𝚙𝚎𝚛𝚝𝚢 сам по себе — обычный класс, у которого реализованы методы 𝚐𝚎𝚝, 𝚜𝚎𝚝, 𝚍𝚎𝚕𝚎𝚝𝚎. Это и называют дескриптором.​

Благодаря такому устройству
🔘можно писать 𝚞𝚜𝚎𝚛.𝚊𝚐𝚎 вместо 𝚞𝚜𝚎𝚛.𝚐𝚎𝚝_𝚊𝚐𝚎(), а под капотом при этом выполняется логика (валидация, преобразования, кэширование и т. п.);​
🔘можно сперва сделать обычное поле 𝚞𝚜𝚎𝚛.𝚊𝚐𝚎, а позже заменить его на вычисляемое свойство, не ломая код, который его читает — внешний интерфейс (𝚞𝚜𝚎𝚛.𝚊𝚐𝚎) не меняется.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
По шагам:

1️⃣В начале вызывается функция 𝚏𝚘𝚘, внутри создаётся список 𝚗𝚞𝚖𝚜 = [𝟷, 𝟸, 𝟹].

2️⃣В блоке 𝚝𝚛𝚢 запускается цикл 𝚏𝚘𝚛 𝚒 𝚒𝚗 𝚛𝚊𝚗𝚐𝚎(𝚕𝚎𝚗(𝚗𝚞𝚖𝚜)), переменная 𝚒 по очереди принимает значения 𝟶, 𝟷, 𝟸.

3️⃣При 𝚒 = 𝟶 условие 𝚒 == 𝟷 ложно, выполняется умножение 𝚗𝚞𝚖𝚜[𝟶] = 𝚗𝚞𝚖𝚜[𝟶] * 𝟷𝟶, и список становится [𝟷𝟶, 𝟸, 𝟹].

4️⃣При 𝚒 = 𝟷 условие 𝚒 == 𝟷 истинно, выполняется 𝚛𝚊𝚒𝚜𝚎 𝚅𝚊𝚕𝚞𝚎𝙴𝚛𝚛𝚘𝚛(𝚍𝚊𝚝𝚊), до строки с умножением элемента список уже не доходит, поэтому элементы с индексами 𝟷 и 𝟸 остаются как были: [𝟷𝟶, 𝟸, 𝟹].

5️⃣Исключение перехватывается в блоке 𝚎𝚡𝚌𝚎𝚙𝚝 𝚅𝚊𝚕𝚞𝚎𝙴𝚛𝚛𝚘𝚛, там подготавливается к возврату кортеж ("error", 𝚗𝚞𝚖𝚜), но фактический выход из функции откладывается до завершения блока 𝚏𝚒𝚗𝚊𝚕𝚕𝚢.

6️⃣В блоке 𝚏𝚒𝚗𝚊𝚕𝚕𝚢 в тот же объект списка 𝚗𝚞𝚖𝚜 добавляется элемент 𝟿𝟿 через вызов 𝚗𝚞𝚖𝚜.𝚊𝚙𝚙𝚎𝚗𝚍(𝟿𝟿), и список становится [𝟷𝟶, 𝟸, 𝟹, 𝟿𝟿].

7️⃣Затем вызывается 𝚙𝚛𝚒𝚗𝚝(𝚗𝚞𝚖𝚜), в стандартный вывод уходит единственная строка с представлением списка [𝟷𝟶, 𝟸, 𝟹, 𝟿𝟿], а возвращаемое значение функции 𝚏𝚘𝚘 нигде не используется.

Из этой задачи видно, что блок 𝚏𝚒𝚗𝚊𝚕𝚕𝚢 всегда выполняется при выходе из 𝚝𝚛𝚢/𝚎𝚡𝚌𝚎𝚙𝚝, даже если уже выбран результат 𝚛𝚎𝚝𝚞𝚛𝚗 или поймано исключение, и может ещё изменить состояние объектов. Мы также чётко видим, что работа с изменяемыми структурами данных внутри 𝚏𝚒𝚗𝚊𝚕𝚕𝚢 напрямую влияет на то, что попадёт в вывод или вернётся из функции, поэтому такие побочные эффекты лучше держать под контролем и не недооценивать их.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1