Python: задачки и вопросы
Ir al canal en Telegram
Вопросы и задачки для подготовки к собеседованиям и прокачки навыков Разместить рекламу: @tproger_sales_bot Правила общения: https://tprg.ru/rules Другие каналы: @tproger_channels Другие наши проекты: https://tprg.ru/media
Mostrar más7 131
Suscriptores
-124 horas
+77 días
Sin datos30 días
Archivo de publicaciones
Сегодня попробуем новый формат: вопрос без правильного ответа. Творческий, где есть место рассуждениям вслух.
Допустим, вы создаёте библиотеку для удобного поиска файлов и каких-то действий с ними. Например, надо найти в директории
C:\logs все файлы больше 10 МБ с раcширением .log и не старше недели. Если использовать стандартные os.stat и datetime, то получается громоздко.
Задача — сделать это как можно более красиво, лаконично и pythonic way.
Некоторые неплохие варианты можно посмотреть в этом посте. Кстати, тоже канал по Python, который я веду лично.
Свои варианты интерфейса для обхода файлов с фильтрами кидайте в комментарии. Вместе обсудим плюсы и минусы, будет такая небольшая практика на выходных.Главный трюк этой конструкции в том, что else здесь относится не к if, а именно к циклу for. Цикл с else в Python можно читать так: для ... если цикл отработал до конца без break, то выполни ...
Разберём код построчно:
🔘Старт цикла: 𝚏𝚘𝚛 𝚒 𝚒𝚗 𝚛𝚊𝚗𝚐𝚎(𝟹): — значения 𝚒 будут 𝟶, 𝟷, 𝟸 по очереди.
🔘Первая итерация: 𝚒 = 𝟶 → выполняется 𝚙𝚛𝚒𝚗𝚝(𝚒, 𝚎𝚗𝚍=' '), на экран уходит 0 .Условие 𝚒 == 𝟷 ложно, 𝚋𝚛𝚎𝚊𝚔 не выполняется, цикл спокойно переходит к следующему значению.
🔘Вторая итерация: 𝚒 = 𝟷 → снова 𝚙𝚛𝚒𝚗𝚝(𝚒, 𝚎𝚗𝚍=' '), теперь на экране 0 1. Условие 𝚒 == 𝟷 становится истинным, выполняется 𝚋𝚛𝚎𝚊𝚔, и цикл немедленно прерывается изнутри, не доходя до конца диапазона.
🔘Что происходит с else:
Блок
𝚎𝚕𝚜𝚎:
𝚙𝚛𝚒𝚗𝚝("𝚎𝚕𝚜𝚎", 𝚎𝚗𝚍=' ')
выполняется только если цикл завершился «нормально» — то есть перебрал все элементы 𝚛𝚊𝚗𝚐𝚎(𝟹) без выхода через 𝚋𝚛𝚎𝚊𝚔. Здесь цикл прерывается на 𝚒 = 𝟷, поэтому 𝚎𝚕𝚜𝚎 пропускается.
🔘Завершение программы: После цикла всегда выполняется 𝚙𝚛𝚒𝚗𝚝("𝚍𝚘𝚗𝚎"), поэтому к уже напечатанному 0 1 добавляется done, итого вывод: 0 1 done.
Как про это думать
Полезная ментальная модель: цикл for … else работает почти как «поиск с флагом»:
🔘Если внутри цикла нашлось «что‑то особенное» и вы сделали 𝚋𝚛𝚎𝚊𝚔, 𝚎𝚕𝚜𝚎 не выполняется.
🔘Если вы честно прошли все элементы и так и не сделали 𝚋𝚛𝚎𝚊𝚔, тогда срабатывает 𝚎𝚕𝚜𝚎 — как ветка «ничего не нашли».
Бро, ты можешь тут реализоваться и т.д.
Став частью ОТП Банка, именно ты сделаешь сильнее всю команду! Расти, учись и пробуй новое — это твой шанс создать что-то по-настоящему крутое.
Присоединяйся к ребятам и делись роликом с теми, кто тоже готов к переменам 🚀
Это пример создания рекурсивного списка — структуры данных, которая ссылается сама на себя. Python позволяет создавать такие объекты благодаря механизму ссылок.
Важен порядок выполнения цепочки присваиваний.
В Python цепочка присваиваний a = b = c = значение выполняется так:
🔘Сначала вычисляется самое правое выражение (создаётся объект).
🔘Затем присваивания происходят слева направо для каждой цели.
Для кода Матрешка = Матрешка[0] = ['Матрешка'] выполнение идёт так
Шаг 1: Вычисляется правая часть
Создаётся список ['Матрешка'] — один объект в памяти
Шаг 2: Первое присваивание (слева)
Матрешка = ['Матрешка'] — переменная Матрешка получает ссылку на этот список
Шаг 3: Второе присваивание
Матрешка[0] = ['Матрешка'] — теперь Матрешка уже определена (из шага 2!), и мы можем обратиться к её первому элементу. Первый элемент (строка 'Матрешка') заменяется ссылкой на весь тот же список.
Ключевой момент
Когда выполняется Матрешка[0] = ..., переменная Матрешка уже существует — она была создана на предыдущем шаге цепочки. Поэтому мы можем обратиться к её элементу и заменить строку 'Матрешка' на ссылку на весь список.
Результат: список, который содержит сам себя в качестве первого элемента — рекурсивная структура [[...]].
Подводные камни:
🔘Такие структуры нельзя корректно сериализовать в JSON.
🔘Наивное копирование создаст проблемы — нужен 𝚌𝚘𝚙𝚢.𝚍𝚎𝚎𝚙𝚌𝚘𝚙𝚢 с обработкой циклов.
🔘Обход без проверки на посещённые узлы приведёт к бесконечной рекурсии.
Уже сегодня через несколько часов начнется конференция «Проектная исповедь» — о том, как жить, работать и ошибаться в мире дилемм.
Всего вас ждет 8 выступлений, кратко расскажем, что к чему
12:10 — Сергей Кожемякин поведает "Исповедь контрол‑фрика: как подчинить тревожность и использовать её в работе"
12:35 — Анна Аксенова поделится, как "Создать из хаоса порядок, или как сверить несверяемое"
13:00 — Алексей Мостовщиков и Андрей Лупий дадут инструкцию по применению GR в ИТ
13:25 — Яна Федорова расскажет, как "Промолчать ради гармонии или говорить о проблемах"
13:50 — Александр Ряховский поможет найти баланс контроля и вдохновения на выступлении "Искусство управлять невозможным"
14:15 — Дмитрий Урузгалиев построит "Мосты вместо стен: как соединить работу и жизнь, не сгорев на границе"
14:40 — Алексей Шершнев объяснит, "Что на самом деле убивает ИТ‑проекты"
15:05 — Иван Москвин покажет, как "Кастомизировать продукт под каждого или делать универсальное решение"
Конференция бесплатная, но надо зарегистрироваться. Сделать это можно на красивом лендинге: https://tprg.ru/KkLb
Это #партнёрский пост
Оператор * в множественном присваивании собирает «остатки» последовательности в список. Он работает как жадный захват всех элементов между фиксированными переменными.
Разбор по шагам:
🔘𝚡 = [𝟷, 𝟸, 𝟹, 𝟺, 𝟻] # список из 5 элементов
🔘𝚊, *𝚋, 𝚌 = 𝚡 # распаковка с остатком:
a берёт первый элемент: a = 1,
c берёт последний элемент: c = 5
*b собирает всё, что между: b = [2, 3, 4]
🔘𝚙𝚛𝚒𝚗𝚝(𝚕𝚎𝚗(𝚋)) # 3
Как работает распаковка
🔘Переменные без * получают ровно один элемент.
🔘Переменная с * захватывает всё оставшееся (может быть пустым списком).
🔘* можно использовать только один раз в присваивании.
Примеры разных случаев
𝚡 = [𝟷, 𝟸]
*𝚊, 𝚋 = 𝚡 # a = 1, b = 2
𝚢 = [𝟷]
*𝚌, 𝚍 = 𝚢 # c = [ ], d = 1 (остаток пустой!)
𝚣 = [𝟷, 𝟸, 𝟹]
𝚎, *𝚏 = 𝚣 # e = 1, f = [2, 3]
Зачем нужно такое поведение
🔘Элегантно обрабатывать последовательности переменной длины.
🔘Частый паттерн: 𝚏𝚒𝚛𝚜𝚝, *𝚛𝚎𝚜𝚝 = 𝚕𝚒𝚜𝚝 для разделения головы и хвоста.
🔘Работает с любыми итерируемыми объектами: списками, кортежами, строками.
Подводные камни
🔘Если элементов меньше, чем фиксированных переменных, получите ValueError.
🔘* всегда создаёт список, даже если элементов нет.
🔘Можно игнорировать остаток через : 𝚊, *, 𝚋 = [𝟷, 𝟸, 𝟹, 𝟺] → игнорируем середину.
Оператор += для списков работает in-place (изменяет объект на месте), в отличие от обычной конкатенации +, которая создаёт новый список.
Разбор по шагам:
🔘𝚊 = создаёт список.
🔘𝚋 = 𝚊 создаёт новую ссылку на тот же список; 𝚊 и 𝚋 указывают на один объект в памяти.
🔘𝚊 += вызывает метод 𝚊.𝚎𝚡𝚝𝚎𝚗𝚍() — расширяет существующий список, добавляя элемент 3. Объект не меняется, изменяется его содержимое.
🔘Поскольку 𝚋 указывает на тот же объект, изменения видны и через 𝚋.
🔘𝚊 == 𝚋 → True (значения равны), 𝚊 𝚒𝚜 𝚋 → True (это один и тот же объект).
Сравнение с обычным +
Если бы использовали 𝚊 = 𝚊 + [3], поведение было бы другим. Создавался бы новый список 𝚊. Переменная 𝚋 продолжала бы указывать на старый список , поэтому 𝚊 == 𝚋 → False, 𝚊 𝚒𝚜 𝚋 → False.
Почему += работает in-place
Для изменяемых типов (списки, множества, словари) оператор += оптимизирован: вместо создания нового объекта он модифицирует существующий. Для неизменяемых типов (строки, кортежи, числа) += всегда создаёт новый объект. Аналогично ведёт себя *=.
Repost from Типичный программист
С кем знакомятся типичные программисты: 2D-тян или живая девушка?
Согласно недавним исследованиям Vantage Point Counseling Services, треть американцев хотя бы раз состояла в романтических отношениях с ИИ. Появилось даже приложение Loverse для виртуальных знакомств, где вместо реальных людей роль партнёров выполняют чат-боты с искусственным интеллектом.
Мы решили провести своё исследование и выяснить где и с кем сегодня знакомятся пользователи стран СНГ. Пожалуйста, пройдите наш небольшой опрос. Это поможет нашему исследованию.
Пройти опрос.
Срезы в Python обрабатываются последовательно слева направо: сначала применяется первый срез к строке, результат становится новой строкой, затем к ней применяется следующий срез.
Разбор по шагам:
🔘Исходная строка: 𝚜 = "𝚙𝚢𝚝𝚑𝚘𝚗".
🔘Первый срез 𝚜[::−𝟷] разворачивает строку задом наперёд → "𝚗𝚘𝚑𝚝𝚢𝚙".
🔘Второй срез [𝟸:] берёт всё начиная с индекса 2 (третий символ) до конца из развёрнутой строки → "𝚑𝚝𝚢𝚙".
🔘Итоговый результат: "𝚑𝚝𝚢𝚙".
Почему это работает именно так:
Срезы создают новые объекты (строки неизменяемы), и каждый срез применяется к результату предыдущего. После разворота строка становится "𝚗𝚘𝚑𝚝𝚢𝚙" (индексы: n=0, o=1, h=2, t=3, y=4, p=5), и [𝟸:] берёт символы начиная с индекса 2, то есть "𝚑𝚝𝚢𝚙".
Практика и сравнение:
🔘𝚜[::−𝟷][𝟸:] ≠ 𝚜[𝟸::−𝟷]: первое разворачивает всю строку, затем обрезает; второе берёт срез с индекса 2 исходной строки и идёт назад.
🔘Пример: для "𝚙𝚢𝚝𝚑𝚘𝚗" срез 𝚜[𝟸::−𝟷] начинает с 't' (индекс 2) и движется назад → "𝚝𝚢𝚙".
🔘Срез 𝚜[::−𝟷][𝟸:] сначала делает "𝚗𝚘𝚑𝚝𝚢𝚙", затем [𝟸:] берёт с позиции 2 → "𝚑𝚝𝚢𝚙".
Подводные камни:
🔘Цепочки срезов читаются слева направо, как вызовы методов; каждый срез возвращает новый объект.
🔘Отрицательный шаг меняет направление обхода, но не меняет порядок применения срезов в цепочке.
🔘Для сложных операций лучше разбивать на отдельные переменные для читаемости: сначала reversed_s = s[::-1], затем result = reversed_s[2:]
В Python булевы значения 𝚃𝚛𝚞𝚎 и 𝙵𝚊𝚕𝚜𝚎 являются подклассами 𝚒𝚗𝚝, причём 𝚃𝚛𝚞𝚎 == 𝟷 и 𝙵𝚊𝚕𝚜𝚎 == 𝟶. Словарь считает равные ключи (с одинаковым хэшем и равенством) идентичными, поэтому при добавлении дублирующего ключа сохраняется первый вставленный ключ, но перезаписывается значение.
Разбор по шагам:
🔘𝟶: '𝚣𝚎𝚛𝚘' — добавляется пара {𝟶: '𝚣𝚎𝚛𝚘'}.
🔘𝟷: '𝚘𝚗𝚎' — добавляется пара {𝟷: '𝚘𝚗𝚎'}.
🔘𝚃𝚛𝚞𝚎: '𝚢𝚎𝚜' — так как 𝚃𝚛𝚞𝚎 == 𝟷 и хэши совпадают, словарь считает это тем же ключом; значение перезаписывается на 'yes', но ключ остаётся 𝟷 (первый вставленный) → {𝟶: '𝚣𝚎𝚛𝚘', 𝟷: '𝚢𝚎𝚜'}.
🔘𝙵𝚊𝚕𝚜𝚎: '𝚗𝚘' — аналогично, 𝙵𝚊𝚕𝚜𝚎 == 𝟶, значение перезаписывается, ключ остаётся 𝟶 → {𝟶: '𝚗𝚘', 𝟷: '𝚢𝚎𝚜'}.
🔘Финальный словарь: {𝟶: '𝚗𝚘', 𝟷: '𝚢𝚎𝚜'} — два уникальных ключа, поэтому 𝚕𝚎𝚗(𝚍) = 𝟸.
🔘𝚍 обращается к ключу 1, который в словаре хранится как числовой ключ 1 (хотя значение было перезаписано булевым True на 'yes'), возвращается 'yes'.
Почему так работает:
Словарь использует хэш и оператор == для определения уникальности ключей. Хэши 𝚃𝚛𝚞𝚎 и 𝟷 одинаковы (оба 1), и они равны по ==, поэтому считаются одним ключом. При коллизии ключей словарь не заменяет сам ключ — он оставляет первый вставленный (в данном случае числовой 1), но обновляет значение последним переданным ('yes' от True). Это значит, что если выведете ключи словаря через 𝚕𝚒𝚜𝚝(𝚍.𝚔𝚎𝚢𝚜()), увидите, а не [False, True].
Практика:
🔘Избегайте смешивания булевых и числовых ключей в словарях — это приводит к неожиданным перезаписям.
🔘Проверить хэш: 𝚑𝚊𝚜𝚑(𝚃𝚛𝚞𝚎) == 𝚑𝚊𝚜𝚑(𝟷) → True, 𝚑𝚊𝚜𝚑(𝙵𝚊𝚕𝚜𝚎) == 𝚑𝚊𝚜𝚑(𝟶) → True.
🔘Если нужно использовать булевы значения как отдельные ключи, храните их в виде строк: {'True': ..., 'False': ...}.
¡Ya disponible! Investigación de Telegram 2025 — los principales insights del año 
