Python: задачки и вопросы
7.51K 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
Разбор по шагам

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
По шагам:

1️⃣На первой строке создаётся пустой список 𝚡 = []. Пустые коллекции в Python имеют ложное булево значение, то есть 𝚋𝚘𝚘𝚕(𝚡) = 𝙵𝚊𝚕𝚜𝚎.​

2️⃣Выражение 𝚢 = 𝚡 𝚘𝚛 [𝟷, 𝟸] работает по правилу: оператор 𝚘𝚛 возвращает первый операнд, если он истинный, иначе второй. Так как 𝚡 — пустой список (ложное значение), результатом становится второй операнд — список [𝟷, 𝟸], и именно он присваивается переменной 𝚢.​

3️⃣Далее вычисляется 𝚣 = 𝚡 𝚊𝚗𝚍 [𝟹, 𝟺]. Для 𝚊𝚗𝚍 правило обратное: возвращается первый ложный операнд, а если оба истинные — второй. Поскольку 𝚡 — ложное значение, короткое замыкание срабатывает сразу, второй операнд не вычисляется, и в 𝚣 попадает тот же пустой список [].​

4️⃣Вызов 𝚙𝚛𝚒𝚗𝚝(𝚢, 𝚣) печатает строковое представление двух объектов через пробел: сначала список [𝟷, 𝟸] как [1, 2], затем пустой список как [], поэтому итоговая и единственная строка вывода — [1, 2] [].​

Чему нас учит эта задачка: логические операторы 𝚊𝚗𝚍 и 𝚘𝚛 в Python возвращают один из операндов, а не обязательно булево значение, и активно используют правду/ложь контейнеров (пустой список, пустая строка и т.п.), что полезно и для удобного задания дефолтных значений, и для лаконичных проверок.
Please open Telegram to view this post
VIEW IN TELEGRAM
2
Разбираем по шагам:

1️⃣В выражении True == 1 используется оператор равенства: булевый тип 𝚋𝚘𝚘𝚕 является подклассом 𝚒𝚗𝚝, и значение True ведёт себя как единица, поэтому результат сравнения — логическое True, и переменная 𝚊 получает значение True.​

2️⃣Во второй строке 1 == True просто меняет местами операнды, но семантика == остаётся прежней: значения всё так же считаются равными, поэтому переменная 𝚋 тоже становится True.​

3️⃣В третьей строке вызывается type(True) — результатом будет объект типа 𝚋𝚘𝚘𝚕, и type(1) — объект типа 𝚒𝚗𝚝; оператор is здесь проверяет, являются ли оба результата вызова type(...) одним и тем же объектом типа, и так как 𝚋𝚘𝚘𝚕 и 𝚒𝚗𝚝 — разные классы, выражение даёт False, поэтому 𝚌 получает False.​

4️⃣При вызове print(a, b, c) функция печатает текущие значения 𝚊, 𝚋 и 𝚌 через пробел, что даёт строку True True False.

Эта задачка важна тем, что показывает: булевы значения в Python — это не магический отдельный тип, а вполне себе подкласс целых чисел, поэтому True, 1 и 1.0 могут неожиданно сливаться в один и тот же ключ словаря или проходить проверки типов там, где ожидается int.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1