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

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

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

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

Другие наши проекты: https://tprg.ru/media
Download Telegram
Главный подвох: обычно в Python 3 переменные цикла внутри list comprehension локальны и не «утекают» наружу, но walrus operator := специально создаёт или перезаписывает переменную в вмещающей области видимости.​

Разбор по шагам в псевдо‑записи:
🔘Инициализация:
𝚡 = 𝟷𝟶 — внешняя переменная равна 10.​
🔘List comprehension с walrus:
𝚗𝚞𝚖𝚜 = 𝚡 := 𝚒 𝚏𝚘𝚛 𝚒 𝚒𝚗 𝚛𝚊𝚗𝚐𝚎(𝟸)
— 𝚒 = 𝟶 → 𝚡 := 𝟶 (перезаписывает внешний 𝚡, теперь 𝚡 = 0)
— 𝚒 = 𝟷 → 𝚡 := 𝟷 (перезаписывает внешний 𝚡, теперь 𝚡 = 1)
— 𝚒 = 𝟸 → 𝚡 := 𝟸 (перезаписывает внешний 𝚡, теперь 𝚡 = 2)
Результат: 𝚗𝚞𝚖𝚜 = 𝟶, 𝟷, 𝟸, а внешний 𝚡 = 𝟸.​
🔘Вывод:
𝚙𝚛𝚒𝚗𝚝(𝚡, 𝚗𝚞𝚖𝚜) → печатает 2 [0, 1, 2].​

Сравнение с обычным list comprehension
> x = 10
> nums = [x for x in range(3)]
> print(x, nums)
Здесь вывод: 10 [0, 1, 2], потому что переменная x внутри comprehension локальна и не затрагивает внешнюю переменную.​

Почему это важно
🔘Walrus operator := создан для присваивания внутри выражений, но его «утечка» в outer scope может быть неожиданной.​
🔘В продакшен‑коде лучше использовать := осторожно, чтобы не перезаписать случайно важные переменные.​
🔘Для чистого list comprehension без побочных эффектов используйте обычную переменную цикла, а не :=.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Главный подвох: Python использует late binding в замыканиях — переменные захватываются по ссылке, и их значение берётся в момент вызова функции, а не в момент создания.​

Разбор по шагам в псевдо‑записи:

1️⃣Создание списка функций:
𝚏𝚞𝚗𝚌𝚜 =
𝚏𝚘𝚛 𝚒 𝚒𝚗 𝚛𝚊𝚗𝚐𝚎(𝟸):
▸ 𝚏𝚞𝚗𝚌𝚜.𝚊𝚙𝚙𝚎𝚗𝚍(𝚕𝚊𝚖𝚋𝚍𝚊 𝚡: 𝚡 * 𝚒)
— создаётся три лямбды, каждая ссылается на переменную i из внешнего scope.
— переменная i меняется: 0 → 1 → 2.​

2️⃣Момент вызова функций:
Когда выполняется [f(1) for f in funcs], цикл for i in range(3) уже завершился.​
Переменная i в глобальном scope теперь равна 2 (последнее значение).​

3️⃣Выполнение каждой лямбды:
Первый вызов: funcs[0](1) → lambda x: x * i, где i = 2 → 1 * 2 = 2.​
Второй вызов: funcs[1](1) → та же лямбда, та же ссылка на i, i = 2 → 1 * 2 = 2.​
Третий вызов: funcs[2](1) → опять i = 2 → 1 * 2 = 2.​

4️⃣Результат: [2, 2, 2].​

Чтобы каждая лямбда «запомнила» своё значение i, нужно захватить его через default argument:

funcs = [lambda x, i=i: x * i for i in range(3)]
print([f(1) for f in funcs]) # [0, 1, 2]

Здесь i=i создаёт локальную переменную внутри лямбды с значением на момент создания, а не ссылкой на внешнюю переменную.​

Почему это важно

🔘Late binding — частая причина ошибок в циклах с lambda, list comprehension и callback‑функциями.​
🔘Правило: если нужно захватить переменную из цикла, всегда используйте default argument для lambda или локальную переменную внутри comprehension.​
🔘Это поведение отличается от некоторых других языков, где захват происходит по значению по умолчанию.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6
Что выведет код?
Anonymous Quiz
48%
a:b
21%
:a
11%
a
6%
ab
15%
Error
👍1
В пятницу не стал жестить, большинство справились с задачей.

Разбор по шагам:

1️⃣Определяется функция‑генератор:
𝚍𝚎𝚏 𝚏():
𝚢𝚒𝚎𝚕𝚍 "𝚊"
𝚢𝚒𝚎𝚕𝚍 "𝚋"

2️⃣Вызов:
𝚜 = ":".𝚓𝚘𝚒𝚗(𝚏()) — здесь берётся встроенный метод 𝚓𝚘𝚒𝚗, разделителем служит строка ":", а аргументом — генератор.​
Генератор по очереди выдаёт "a", потом "b".

3️⃣Метод join работает так:
— «Собери все строки из генератора, вставь между ними разделитель»​
— Результат — строка "a:b".

4️⃣Вывод:
𝚙𝚛𝚒𝚗𝚝(𝚜) → 'a:b'​

Почему это важно
🔘join очень полезен: работает с любым итерируемым объектом: списком, кортежем, даже генератором.
🔘Передавая генератор, можно собирать строки «на лету» без лишней памяти и промежуточных списков.
🔘Если передать нестроковые элементы, будет TypeError, но для генератора, выдающего строки — всё корректно.
Please open Telegram to view this post
VIEW IN TELEGRAM
5
Давайте разберём пошагово:
1️⃣𝚡 = [𝟷, 𝟸, 𝟹] — создаётся список.
2️⃣𝚢 = 𝚡 — теперь 𝚢 и 𝚡 указывают на один и тот же объект списка [𝟷, 𝟸, 𝟹].
3️⃣𝚡, 𝚢[𝟶] = 𝚢, 𝟺 — здесь начинается хитрость множественного присваивания.

Python сначала вычисляет правую часть целиком: создаётся временный кортеж из текущих значений 𝚢 и 𝟺, то есть [𝟷, 𝟸, 𝟹] и 𝟺.​

Только после этого начинается присваивание слева направо:
4️⃣𝚡 = [𝟷, 𝟸, 𝟹] — переменная 𝚡 теперь указывает на тот же список, что и раньше (потому что 𝚢 указывал на него).
5️⃣𝚢[𝟶] = 𝟺 — модифицируется первый элемент списка, на который указывает 𝚢.

Но поскольку 𝚡 и 𝚢 указывают на один и тот же список, изменение 𝚢[𝟶] сразу видно и через 𝚡. В итоге список становится [𝟺, 𝟸, 𝟹], и обе переменные выводят его.​

Эта задачка показывает важную особенность: порядок вычисления и присваивания в множественном присваивании может приводить к неочевидным результатам, когда левая и правая части «переплетены» через ссылки на изменяемые объекты.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6👎1
Что выведет код?
Anonymous Quiz
50%
1 10
19%
1 5
22%
1 None
9%
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
👍1