Генераторы вместо списков: экономим память 📉
Если нужно просто «пройтись по данным и посчитать», создавать целый список — лишняя трата памяти и времени. Генераторы отдают элементы по одному «на лету», поэтому код стартует быстрее и не раздувает RAM.
Что такое генераторы
Это «ленивые» источники данных. Они не хранят всю последовательность, а вычисляют следующий элемент по запросу.
Есть два простых способа получить генератор:
🔹Генераторное выражение: круглые скобки ( … )
🔹Функция с yield: возвращает элементы по одному
Зачем это нужно
🔹Экономия памяти: не создаём промежуточные списки.
🔹Быстрый старт: обработка начинается сразу, даже для крупных диапазонов/файлов.
🔹Удобные «конвейеры»: фильтры и преобразования без временных структур.
Мини-пример
Памятка:
🔹квадратные скобки [...] → список,
🔹круглые (...) → генератор.
Когда использовать
🔹Один проход по данным: sum/max/any, простой цикл.
🔹Потоки и большие объёмы: чтение файла построчно, «страницы» из API.
🔹Пайплайн «filter → map → reduce» без промежуточных списков.
Когда список уместнее
🔹Нужен доступ по индексу или случайное обращение.
🔹Нужны многократные проходы по тем же данным.
🔹Важно знать len() заранее, хранить срезы или кэшировать результат.
📌 Итог
Берите генераторы, когда результат не нужно хранить и вы проходите данные один раз: так код потребляет меньше памяти и работает стабильнее. Списки оставляйте для задач с индексами, повторным обходом и известной длиной.
Если нужно просто «пройтись по данным и посчитать», создавать целый список — лишняя трата памяти и времени. Генераторы отдают элементы по одному «на лету», поэтому код стартует быстрее и не раздувает RAM.
Что такое генераторы
Это «ленивые» источники данных. Они не хранят всю последовательность, а вычисляют следующий элемент по запросу.
Есть два простых способа получить генератор:
🔹Генераторное выражение: круглые скобки ( … )
🔹Функция с yield: возвращает элементы по одному
Зачем это нужно
🔹Экономия памяти: не создаём промежуточные списки.
🔹Быстрый старт: обработка начинается сразу, даже для крупных диапазонов/файлов.
🔹Удобные «конвейеры»: фильтры и преобразования без временных структур.
Мини-пример
sum([x*x for x in range(10_000_000)])
# ❌ создаёт огромный список в памяти
sum(x*x for x in range(10_000_000))
# ✅ генератор: элементы не хранятся → меньше памяти
def squares(n):
for i in range(n):
yield i*i
list(squares(5)) # → [0, 1, 4, 9, 16]
Памятка:
🔹квадратные скобки [...] → список,
🔹круглые (...) → генератор.
Когда использовать
🔹Один проход по данным: sum/max/any, простой цикл.
🔹Потоки и большие объёмы: чтение файла построчно, «страницы» из API.
🔹Пайплайн «filter → map → reduce» без промежуточных списков.
Когда список уместнее
🔹Нужен доступ по индексу или случайное обращение.
🔹Нужны многократные проходы по тем же данным.
🔹Важно знать len() заранее, хранить срезы или кэшировать результат.
📌 Итог
Берите генераторы, когда результат не нужно хранить и вы проходите данные один раз: так код потребляет меньше памяти и работает стабильнее. Списки оставляйте для задач с индексами, повторным обходом и известной длиной.
👍10❤4🔥2💘1
🧩 Что выведет этот код?
Сегодня разберем одну из самых коварных особенностей Python, связанную с аргументами функций.
Посмотрите на код ниже:
Каким будет вывод второй строки?
1️⃣ ['банан']
2️⃣ ['яблоко', 'банан']
3️⃣ Error
4️⃣ None
Правильный ответ и объяснение:
Ответ:
2️⃣ ['яблоко', 'банан']
Почему так происходит?
В Python значения по умолчанию для аргументов вычисляются всего один раз — в момент определения функции (когда интерпретатор видит строку def ...), а не при каждом её вызове.
Если в качестве значения по умолчанию используется изменяемый объект (mutable), такой как список [] или словарь {}, то он становится «общим» для всех последующих вызовов функции.
🔹При первом вызове в «общую» корзину попало «яблоко».
🔹При втором вызове функция обратилась к тому же самому объекту списка и добавила туда «банан».
Как этого избежать?
Всегда используйте None в качестве значения по умолчанию:
Теперь при каждом вызове без аргумента будет создаваться новый пустой список.
Сегодня разберем одну из самых коварных особенностей Python, связанную с аргументами функций.
Посмотрите на код ниже:
def add_item(item, basket=[]):
basket.append(item)
return basket
print(add_item("яблоко"))
print(add_item("банан"))
Каким будет вывод второй строки?
1️⃣ ['банан']
2️⃣ ['яблоко', 'банан']
3️⃣ Error
4️⃣ None
Правильный ответ и объяснение:
2️⃣ ['яблоко', 'банан']
Почему так происходит?
В Python значения по умолчанию для аргументов вычисляются всего один раз — в момент определения функции (когда интерпретатор видит строку def ...), а не при каждом её вызове.
Если в качестве значения по умолчанию используется изменяемый объект (mutable), такой как список [] или словарь {}, то он становится «общим» для всех последующих вызовов функции.
🔹При первом вызове в «общую» корзину попало «яблоко».
🔹При втором вызове функция обратилась к тому же самому объекту списка и добавила туда «банан».
Как этого избежать?
Всегда используйте None в качестве значения по умолчанию:
def add_item(item, basket=None):
if basket is None:
basket = []
basket.append(item)
return basket
Теперь при каждом вызове без аргумента будет создаваться новый пустой список.
👍17❤4🔥4💘2
Python-Academy-Interview-Questions-ru.pdf
1.3 MB
Топ-вопросы с собеседований по Python + ответы 📝
Мы собрали самые популярные вопросы и варианты ответов — читайте по ссылке:
https://python-academy.org/ru/interview-questions
Чтобы было удобнее, подготовили офлайн-версию (PDF). Сохраните и используйте, чтобы быстро освежить знания перед важным собесом.
📥 офлайн-версия — в файле к этому посту
удачи на собеседованиях! 💪
Мы собрали самые популярные вопросы и варианты ответов — читайте по ссылке:
https://python-academy.org/ru/interview-questions
Чтобы было удобнее, подготовили офлайн-версию (PDF). Сохраните и используйте, чтобы быстро освежить знания перед важным собесом.
📥 офлайн-версия — в файле к этому посту
удачи на собеседованиях! 💪
🔥22❤5👍2
✂️ Иллюзия .strip(): почему 'mac.com'.strip('.com') вернёт 'a'
Хочешь очистить данные перед работой и отрезать домен с помощью
⚠️ В чём подвох?
Многие думают, что
Метод агрессивно отщипывает эти буквы с обоих концов строки до тех пор, пока не встретит чужой символ.
Символы
🚀 Безопасное решение
Для удаления точных окончаний и приставок в Python 3.9 добавили специальные методы. Они ищут строго целую подстроку:
💡 Оставь
Хочешь очистить данные перед работой и отрезать домен с помощью
.strip('.com')? Осторожно: этот метод часто молча «откусывает» часть нужной строки.⚠️ В чём подвох?
Многие думают, что
.strip() удаляет переданную подстроку целиком. На деле он воспринимает аргумент как набор независимых символов: 'c', 'o', 'm', '.'.Метод агрессивно отщипывает эти буквы с обоих концов строки до тех пор, пока не встретит чужой символ.
domain = 'mac.com'
print(domain.strip('.com'))
# Вывод: 'a'
Символы
m, c и точка были удалены не только из окончания, но и из самого слова «mac»! 🚀 Безопасное решение
Для удаления точных окончаний и приставок в Python 3.9 добавили специальные методы. Они ищут строго целую подстроку:
domain = 'mac.com'
print(domain.removesuffix('.com')) # 'mac'
print('anti-virus'.removeprefix('anti-')) # 'virus'
💡 Оставь
.strip() исключительно для зачистки невидимых пробелов и переносов строк, а для суффиксов бери .removesuffix().❤6👍6🔥2
🪤 Загадка с собеса: ловушка с lambda в цикле
Создаешь список функций, ожидаешь получить 0, 1 и 2, а Python возвращает одни двойки. Это классический баг, на котором часто ошибаются новички.
⚠️ Как выглядит проблема
⚙️ Почему так происходит
Вся суть в механизме позднего связывания (late binding).
🔹 Функция
🔹 Она запоминает лишь ссылку на саму переменную в окружающем коде.
🔹 Когда цикл завершается, переменная
🚀 Как починить
Нужно заставить Python зафиксировать значение сразу. Самый элегантный способ — передать его через аргумент по умолчанию:
🔸 Аргументы по умолчанию вычисляются ровно в момент создания функции, поэтому каждая
💡 Значения по умолчанию фиксируются при создании функции, а код внутри нее выполняется только при вызове.
Создаешь список функций, ожидаешь получить 0, 1 и 2, а Python возвращает одни двойки. Это классический баг, на котором часто ошибаются новички.
⚠️ Как выглядит проблема
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs]) # [2, 2, 2]
⚙️ Почему так происходит
Вся суть в механизме позднего связывания (late binding).
🔹 Функция
lambda не сохраняет значение i в момент своего создания.🔹 Она запоминает лишь ссылку на саму переменную в окружающем коде.
🔹 Когда цикл завершается, переменная
i останавливается на значении 2. При вызове все три функции обращаются к этой финальной двойке.🚀 Как починить
Нужно заставить Python зафиксировать значение сразу. Самый элегантный способ — передать его через аргумент по умолчанию:
funcs = [lambda i=i: i for i in range(3)]
print([f() for f in funcs]) # [0, 1, 2]
🔸 Аргументы по умолчанию вычисляются ровно в момент создания функции, поэтому каждая
lambda получает свою личную независимую копию числа.💡 Значения по умолчанию фиксируются при создании функции, а код внутри нее выполняется только при вызове.
👍5
🎭 Загадка с собеса: почему True и 1 склеиваются в словаре
Попробуй создать словарь с ключами
⚠️ Что произойдёт на практике
⚙️ Почему так работает
🔹 В Python логический тип
🔹 Математически
🔹 Словари ищут элементы по хэшу. Так как
📉 Как происходит перезапись
🔸 Сначала создаётся пара, где ключ —
🔸 Затем Python доходит до ключа
🔸 Сам ключ не меняется (в словаре остаётся
💡 Будь осторожен при группировке данных: ключи разных типов могут внезапно оказаться близнецами под капотом.
Попробуй создать словарь с ключами
True и 1. Интуитивно мы ждём два разных элемента, но Python тихо оставит только один.⚠️ Что произойдёт на практике
my_dict = {True: 'да', 1: 'нет'}
print(my_dict)
# Вывод: {True: 'нет'}⚙️ Почему так работает
🔹 В Python логический тип
bool — это прямой подкласс целых чисел (int).🔹 Математически
True абсолютно равно 1, а False равно 0.🔹 Словари ищут элементы по хэшу. Так как
hash(True) == hash(1), Python считает их одним и тем же ключом.📉 Как происходит перезапись
🔸 Сначала создаётся пара, где ключ —
True, а значение — 'да'.🔸 Затем Python доходит до ключа
1. Он видит совпадение и решает, что такой элемент «уже есть».🔸 Сам ключ не меняется (в словаре остаётся
True), но его значение молча обновляется на 'нет'.💡 Будь осторожен при группировке данных: ключи разных типов могут внезапно оказаться близнецами под капотом.
👍4🔥4❤1
🏦 Магия round(): почему 2.5 превращается в 2
Забудь школьные правила математики. Если в Python написать
⚙️ Как это работает
Python использует «банковское округление» (к ближайшему чётному числу), если дробная часть равна ровно половине.
🔹
🔹
🔹
📉 Зачем ломать математику?
Это спасает аналитиков от накопления статистической ошибки.
🔸 При школьном округлении (всегда вверх) итоговые суммы в больших датасетах постепенно завышаются.
🔸 Банковское правило идеально балансирует погрешность: половина «половинок» уходит вниз, а половина — вверх. Среднее значение остаётся честным.
🚀 Как вернуть классику
Если тебе нужно строгое математическое округление, встроенная функция не подойдёт. Используй модуль
💡 Для аналитики и работы с данными встроенный
Забудь школьные правила математики. Если в Python написать
round(2.5), ответом будет 2, а не 3. Эта неочевидная деталь часто становится ловушкой для новичков.⚙️ Как это работает
Python использует «банковское округление» (к ближайшему чётному числу), если дробная часть равна ровно половине.
🔹
round(1.5) # Вернёт 2🔹
round(2.5) # Тоже вернёт 2🔹
round(3.5) # Вернёт 4📉 Зачем ломать математику?
Это спасает аналитиков от накопления статистической ошибки.
🔸 При школьном округлении (всегда вверх) итоговые суммы в больших датасетах постепенно завышаются.
🔸 Банковское правило идеально балансирует погрешность: половина «половинок» уходит вниз, а половина — вверх. Среднее значение остаётся честным.
🚀 Как вернуть классику
Если тебе нужно строгое математическое округление, встроенная функция не подойдёт. Используй модуль
decimal:from decimal import Decimal, ROUND_HALF_UP
num = Decimal('2.5')
print(num.quantize(Decimal('1'), rounding=ROUND_HALF_UP)) # Вернёт 3
💡 Для аналитики и работы с данными встроенный
round() идеален, а для точных финансовых расчётов всегда бери decimal.👍4❤3👌2
🛑 Убийца Event Loop: почему time.sleep() ломает асинхронность
Асинхронный код должен летать, но иногда он работает медленнее обычного. Частая причина — блокирующий вызов, который замораживает вообще всё.
⚙️ Как работает Event Loop
Представь официанта. Он принял заказ у одного столика и, пока блюдо готовится, идёт к другому. Это здоровая асинхронность. Но если официант будет стоять на кухне и тупо смотреть на кастрюлю, другие гости останутся голодными.
⚠️ Проблема синхронного кода
Вызов
🔹 Event Loop полностью блокируется.
🔹 Пока функция ждёт, ни одна другая корутина не может выполниться.
🚀 Как правильно
Используй инструменты, которые возвращают управление циклу через
💡 Внутри асинхронных функций используй только те инструменты, которые умеют отдавать управление и не блокируют поток.
Асинхронный код должен летать, но иногда он работает медленнее обычного. Частая причина — блокирующий вызов, который замораживает вообще всё.
⚙️ Как работает Event Loop
Представь официанта. Он принял заказ у одного столика и, пока блюдо готовится, идёт к другому. Это здоровая асинхронность. Но если официант будет стоять на кухне и тупо смотреть на кастрюлю, другие гости останутся голодными.
⚠️ Проблема синхронного кода
Вызов
time.sleep() или requests.get() внутри async def — это тот самый зависший официант. 🔹 Event Loop полностью блокируется.
🔹 Пока функция ждёт, ни одна другая корутина не может выполниться.
🚀 Как правильно
Используй инструменты, которые возвращают управление циклу через
await. Вместо requests бери aiohttp или httpx, а для пауз используй асинхронный сон.import asyncio
import time
async def bad_wait():
time.sleep(1) # ❌ Стопорит всю программу
async def good_wait():
await asyncio.sleep(1) # ✅ Ставит на паузу только себя
💡 Внутри асинхронных функций используй только те инструменты, которые умеют отдавать управление и не блокируют поток.
👍5❤2🔥1
🪄 Магия venv: почему виртуальное окружение не «виртуальное»
Многие думают, что venv — это строгая изолированная песочница, эдакий мини-Docker. На деле никакой сложной магии изоляции там нет.
⚙️ Как это работает на самом деле
Это просто папка. При создании окружения Python копирует туда свой исполняемый файл и утилиту pip. Команда активации не запускает скрытых процессов и не строит виртуальных стен — она лишь подменяет системную переменную
🚀 Секрет запуска без активации
Раз это обычная папка, тебе вообще не обязательно использовать команду активации для скриптов в cron или systemd. Достаточно вызвать нужный Python напрямую, и он сам подтянет библиотеки из своего окружения:
⚠️ О чём стоит помнить
🔹 Если переместить папку venv в другое место, окружение сломается — пути внутри захардкожены абсолютно.
🔸 Скрипт из venv имеет полный доступ к системе: он всё ещё может случайно удалить твои личные базы или файлы.
💡 venv — это не изоляция от операционной системы, а просто удобный способ разложить библиотеки по разным полкам.
Многие думают, что venv — это строгая изолированная песочница, эдакий мини-Docker. На деле никакой сложной магии изоляции там нет.
⚙️ Как это работает на самом деле
Это просто папка. При создании окружения Python копирует туда свой исполняемый файл и утилиту pip. Команда активации не запускает скрытых процессов и не строит виртуальных стен — она лишь подменяет системную переменную
PATH в терминале, чтобы система искала программы сначала в этой новой папке.🚀 Секрет запуска без активации
Раз это обычная папка, тебе вообще не обязательно использовать команду активации для скриптов в cron или systemd. Достаточно вызвать нужный Python напрямую, и он сам подтянет библиотеки из своего окружения:
/путь/к/venv/bin/python script.py
⚠️ О чём стоит помнить
🔹 Если переместить папку venv в другое место, окружение сломается — пути внутри захардкожены абсолютно.
🔸 Скрипт из venv имеет полный доступ к системе: он всё ещё может случайно удалить твои личные базы или файлы.
💡 venv — это не изоляция от операционной системы, а просто удобный способ разложить библиотеки по разным полкам.
👍5❤2👌2
🐢 Ложная асинхронность: почему два await подряд не ускорят код
Ты добавил
⚠️ Очередь вместо параллельности
Если написать два
🚀 Как запустить одновременно
Чтобы задачи действительно выполнялись параллельно, их нужно отправить в цикл событий разом. Тут спасает
💡
Ты добавил
async и await, ожидая, что запросы полетят параллельно, но скрипт всё равно тормозит. Проблема в том, как именно ты ждёшь результат.⚠️ Очередь вместо параллельности
Если написать два
await друг за другом, магии не случится. Код остановится на первой строчке и будет ждать её завершения, прежде чем запустить вторую. Это всё равно что поставить вариться макароны и тупо стоять у плиты, вместо того чтобы параллельно нарезать салат.# Выполнится за 3 секунды (1 + 2)
result1 = await fetch_data(1)
result2 = await fetch_data(2)
🚀 Как запустить одновременно
Чтобы задачи действительно выполнялись параллельно, их нужно отправить в цикл событий разом. Тут спасает
asyncio.gather(). Он стартует все корутины одновременно и возвращает список результатов.import asyncio
# Выполнится за 2 секунды (по самой долгой задаче)
result1, result2 = await asyncio.gather(
fetch_data(1),
fetch_data(2)
)
💡
await ставит на паузу текущую функцию, а не запускает фоновый процесс — группируй вызовы, чтобы получить настоящую скорость.❤4👍4🤯1
💣 Бомба в requests: почему приложение виснет навсегда
Самая популярная библиотека Python таит в себе скрытую угрозу. Если написать простой
⚠️ В чём опасность
🔹 По умолчанию у requests нет лимита времени на ожидание ответа.
🔹 Если сторонний API «затупит» на уровне сети, твой запрос не упадёт с ошибкой, а будет покорно ждать бесконечно.
🔹 В веб-приложении это блокирует рабочий поток. Несколько таких запросов — и все воркеры бэкенда исчерпаются, а ваш сервис перестанет отвечать пользователям.
🚀 Как писать безопасно
Всегда, абсолютно всегда явно указывай параметр
⚙️ Тонкая настройка
Вместо одного числа можно передать кортеж:
💡 Никогда не доверяй чужим серверам — всегда ставь таймаут, чтобы защитить свой бэкенд.
Самая популярная библиотека Python таит в себе скрытую угрозу. Если написать простой
requests.get(url), твой код рискует зависнуть навечно.⚠️ В чём опасность
🔹 По умолчанию у requests нет лимита времени на ожидание ответа.
🔹 Если сторонний API «затупит» на уровне сети, твой запрос не упадёт с ошибкой, а будет покорно ждать бесконечно.
🔹 В веб-приложении это блокирует рабочий поток. Несколько таких запросов — и все воркеры бэкенда исчерпаются, а ваш сервис перестанет отвечать пользователям.
🚀 Как писать безопасно
Всегда, абсолютно всегда явно указывай параметр
timeout. Он прервёт ожидание и выбросит исключение.import requests
try:
# Ждём максимум 5 секунд
res = requests.get("https://api.example.com", timeout=5)
except requests.exceptions.Timeout:
print("API не ответил вовремя!")
⚙️ Тонкая настройка
Вместо одного числа можно передать кортеж:
timeout=(3, 10). Это даст 3 секунды на установку соединения и 10 секунд на чтение данных от сервера.💡 Никогда не доверяй чужим серверам — всегда ставь таймаут, чтобы защитить свой бэкенд.
🔥13❤5🤯2
🕰 Проблема 2069 года: как Python тайно меняет век
Парсишь дату из старой выгрузки или Excel, где год указан двумя цифрами? Осторожно: невинный код может случайно отправить твои данные в прошлое.
⚠️ Ловушка модуля datetime
Если использовать формат
⚙️ Откуда взялся этот водораздел
🔹 Это не баг языка, а жесткое наследие стандарта POSIX.
🔹 Значения от 00 до 68 автоматически зачисляются в 2000-е годы.
🔹 Значения от 69 до 99 уходят в 1900-е.
🔹 Граница выбрана привязкой к началу эпохи Unix (1 января 1970 года).
🚀 Как избежать бага
🔸 В API, базах данных и формах всегда требуй четырехзначный год.
🔸 Парси даты строго через заглавную букву:
💡 Явное всегда лучше неявного: четыре цифры года защитят твой код от непредсказуемых путешествий во времени на проде.
Парсишь дату из старой выгрузки или Excel, где год указан двумя цифрами? Осторожно: невинный код может случайно отправить твои данные в прошлое.
⚠️ Ловушка модуля datetime
Если использовать формат
%y (строчная y), Python попытается угадать век сам. И у него есть строгий, но очень неочевидный лимит:from datetime import datetime
# 68 превращается в 2068 год
print(datetime.strptime("31.12.68", "%d.%m.%y").year)
# А 69 внезапно улетает в 1969!
print(datetime.strptime("01.01.69", "%d.%m.%y").year)
⚙️ Откуда взялся этот водораздел
🔹 Это не баг языка, а жесткое наследие стандарта POSIX.
🔹 Значения от 00 до 68 автоматически зачисляются в 2000-е годы.
🔹 Значения от 69 до 99 уходят в 1900-е.
🔹 Граница выбрана привязкой к началу эпохи Unix (1 января 1970 года).
🚀 Как избежать бага
🔸 В API, базах данных и формах всегда требуй четырехзначный год.
🔸 Парси даты строго через заглавную букву:
%Y.💡 Явное всегда лучше неявного: четыре цифры года защитят твой код от непредсказуемых путешествий во времени на проде.
👍5🤯2❤1🔥1
🤥 Иллюзия sys.getsizeof(): почему Python скрывает реальный вес
Ты загрузил огромный датасет в список, проверил размер через
⚙️ Коробка с адресами
В Python переменные — это ссылки. Список не хранит сами строки или словари внутри себя. Он хранит лишь указатели на них (адреса в памяти).
🔹
🔹 Весь вложенный груз (реальные гигабайты данных) эта встроенная функция полностью игнорирует.
🚀 Как узнать настоящий размер
Чтобы рекурсивно обойти все вложенные объекты и посчитать их честный вес, используй библиотеку
💡 Никогда не доверяй sys.getsizeof() при оценке сложных структур данных, чтобы не пропустить утечку памяти.
Ты загрузил огромный датасет в список, проверил размер через
sys.getsizeof() и увидел скромные 8 мегабайт. Но сервер всё равно падает от нехватки оперативной памяти. В чём подвох?⚙️ Коробка с адресами
В Python переменные — это ссылки. Список не хранит сами строки или словари внутри себя. Он хранит лишь указатели на них (адреса в памяти).
🔹
sys.getsizeof() взвешивает только саму «коробку» с этими адресами.🔹 Весь вложенный груз (реальные гигабайты данных) эта встроенная функция полностью игнорирует.
🚀 Как узнать настоящий размер
Чтобы рекурсивно обойти все вложенные объекты и посчитать их честный вес, используй библиотеку
pympler.import sys
from pympler import asizeof
# Создаем список из 100 000 больших строк
data = ["python" * 1000 for _ in range(100_000)]
# Покажет ~800 КБ (только массив указателей)
print(sys.getsizeof(data))
# Покажет реальные 600+ МБ (все вложенные строки)
print(asizeof.asizeof(data))
💡 Никогда не доверяй sys.getsizeof() при оценке сложных структур данных, чтобы не пропустить утечку памяти.
❤3👍1🤯1