🪄 Магия 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
🚨 Молчаливый assert: почему тесты тебя обманывают
Ты пишешь тест или валидацию данных и спишь спокойно. Но одна лишняя пара скобок может сделать так, что критический баг тихо окажется на проде.
⚠️ Коварная ловушка
Посмотри на этот код:
Кажется, всё логично. Но эта проверка никогда не выдаст ошибку, даже если у пользователя нет прав.
⚙️ Почему так происходит
🔹 В Python
🔹 Если написать условие и текст ошибки в круглых скобках через запятую, Python воспримет это как кортеж (tuple).
🔹 Любой непустой кортеж при проверке всегда оценивается как
🔹 В итоге интерпретатор выполняет
🚀 Как писать правильно
Просто убери скобки. Пиши условие и сообщение об ошибке через запятую:
💡 Используй линтеры вроде flake8 или ruff — они автоматически находят такие «спящие» проверки и экономят нервы.
Ты пишешь тест или валидацию данных и спишь спокойно. Но одна лишняя пара скобок может сделать так, что критический баг тихо окажется на проде.
⚠️ Коварная ловушка
Посмотри на этот код:
assert(user.is_admin, "Доступ запрещен")
Кажется, всё логично. Но эта проверка никогда не выдаст ошибку, даже если у пользователя нет прав.
⚙️ Почему так происходит
🔹 В Python
assert — это не функция, а ключевое слово (statement).🔹 Если написать условие и текст ошибки в круглых скобках через запятую, Python воспримет это как кортеж (tuple).
🔹 Любой непустой кортеж при проверке всегда оценивается как
True.🔹 В итоге интерпретатор выполняет
assert True и радостно идёт дальше, игнорируя баг.🚀 Как писать правильно
Просто убери скобки. Пиши условие и сообщение об ошибке через запятую:
assert user.is_admin, "Доступ запрещен"
💡 Используй линтеры вроде flake8 или ruff — они автоматически находят такие «спящие» проверки и экономят нервы.
👌3🤯2
🤯 Загадка с собеса: как finally тайно переписывает return
Представь, что функция успешно выполнила код и готова отдать результат. Но тут в дело вступает блок очистки и молча ломает логику.
⚙️ Почему функция вернет 2
🔹 Блок
🔹 Когда в
🔹 Если там есть свой
⚠️ В чём реальная опасность
🔸 Тихое поглощение исключений: Если внутри
🔸 Сломанный поток: Операторы
💡 Блок
Представь, что функция успешно выполнила код и готова отдать результат. Но тут в дело вступает блок очистки и молча ломает логику.
def get_number():
try:
return 1
finally:
return 2
print(get_number())
⚙️ Почему функция вернет 2
🔹 Блок
finally выполняется всегда, независимо от того, была ли ошибка.🔹 Когда в
try срабатывает return, Python запоминает это значение, но перед выходом обязательно заходит в finally.🔹 Если там есть свой
return, он получает абсолютный приоритет и перезаписывает первоначальный ответ.⚠️ В чём реальная опасность
🔸 Тихое поглощение исключений: Если внутри
try произойдет сбой (например, деление на ноль), а в finally стоит return, ошибка просто исчезнет. Программа пойдет дальше, скрыв критический баг.🔸 Сломанный поток: Операторы
break или continue в блоке очистки создают такие же непредсказуемые последствия.💡 Блок
finally нужен только для закрытия файлов и освобождения ресурсов — никогда не возвращай из него значения.🔥4👍3🤯1