🌀 Context Manager: не только `with open()`. Пишем свои для транзакций и таймеровКонтекстные менеджеры в Python — это магия with, которая управляет жизненным циклом ресурсов. Все знают про файлы, но настоящая сила открывается, когда пишешь свои менеджеры для транзакций, замеров времени и автоматического отката.🎆 Быстрый старт: как это работаетЛюбой контекстный менеджер — это класс с методами __enter__ и __exit__.class MyManager: def __enter__(self): print("Входим в контекст") return self # Этот объект попадёт в as def __exit__(self, exc_type, exc_val, exc_tb): print("Выходим из контекста") return False # Если True — исключение будет подавленоwith MyManager() as mgr: print("Внутри контекста")➡️ __enter__ выполняется при входе в with, __exit__ — всегда при выходе, даже если было исключение.✅ Менеджер для транзакций БД (автооткат)Представь, что нужно гарантировать откат изменений при ошибке:import sqlite3class Transaction: def __init__(self, conn): self.conn = conn def __enter__(self): self.conn.execute("BEGIN") return self.conn def __exit__(self, exc_type, exc_val, exc_tb): if exc_type is None: self.conn.commit() print("✅ Коммит") else: self.conn.rollback() print("❌ Откат из-за ошибки:", exc_val) return False # Пробрасываем исключение дальше# Использованиеconn = sqlite3.connect(":memory:")conn.execute("CREATE TABLE users (id INTEGER, name TEXT)")try: with Transaction(conn) as tx: tx.execute("INSERT INTO users VALUES (1, 'Alice')") raise ValueError("Искусственная ошибка!")except: passprint(conn.execute("SELECT * FROM users").fetchall()) # []➡️ Если в блоке with вылетает исключение — __exit__ делает rollback. Нет ошибки — commit.🧪 Менеджер-таймер для замера скоростиИдеально для быстрого профайлинга кусков кода:import timefrom contextlib import contextmanagerclass Timer: def __enter__(self): se
PytStart | Программирование на Python
@pytstart
Python: примеры кода, уроки, статьиКупить рекламу: https://telega.in/c/pytstart✍️По всем вопросам: @Pascal4eg
Последние посты
🐳 Docker для Python-разработчика (оптимизация образов)Твой Docker-образ весит 1.5 ГБ, а сборка каждый раз тянет все зависимости с нуля? Пора это исправить. Правильный Dockerfile экономит время, трафик и нервы.⚡️ Выбор базового образа: slim vs alpineГлавное правило: никогда не использовать python:latest. Он огромный.# ❌ Плохо: 1 ГБ+FROM python:3.11# ✅ Хорошо: ~200 МБFROM python:3.11-slim# ✅ Для фанатов минимализма: ~100 МБ (но могут быть проблемы с компиляцией)FROM python:3.11-alpine➡️ slim — урезанный Debian, alpine — минимальный Linux на musl libc. Для большинства проектов хватает slim.✅ Многоступенчатая сборка (multi-stage): отделяем сборку от рантаймаЗачем тащить в продакшен компиляторы и исходники?# Этап 1: сборкаFROM python:3.11-slim as builderWORKDIR /appCOPY requirements.txt .# Создаём один слой со всеми зависимостямиRUN pip install --user --no-warn-script-location -r requirements.txt# Этап 2: финальный образFROM python:3.11-slimWORKDIR /app# Копируем только установленные пакеты из builderCOPY --from=builder /root/.local /root/.local# Копируем кодCOPY . .# Важно: добавляем путь к пакетамENV PATH=/root/.local/bin:$PATHCMD ["python", "main.py"]➡️ В финальном образе только нужные пакеты и твой код. Компиляторы, временные файлы и кэш pip остаются в builder.🧪 Оптимизация кэширования зависимостей: почему Docker снова качает pandas?Docker кэширует слои. Если требования не меняются — не переустанавливай.FROM python:3.11-slimWORKDIR /app# 1. Копируем ТОЛЬКО requirements.txtCOPY requirements.txt . # Этот слой кэшируется отдельно!# 2. Устанавливаем зависимостиRUN pip install --no-cache-dir -r requirements.txt# 3. Копируем весь остальной кодCOPY . . # Этот слой пересобирается при любом изменении кодаCMD ["python", "main.py"]➡️ Docker кэширует слой RUN pip install... до тех пор, пока requirements.txt не изменится. Изменил одну строчку в коде — зависимости не переустанавливаются.🔍 Убираем мусор: один RUN, меньше слоевКаждая команда в Dockerfile создаёт слой. Объединяй
🔌 Python + SQLite: скрытые возможности (возврат изменённых строк, оконные функции)Все пишут SELECT и INSERT, но SQLite — это полноценная СУБД с фичами из больших баз. Используй RETURNING, UPSERT и оконные функции, чтобы логика работала на уровне данных, а не в коде.⚡️ UPSERT: вставь или обнови одной командойКлассика: нужно обновить запись, если она есть, иначе создать. Раньше требовалось два запроса. Теперь — один:import sqlite3conn = sqlite3.connect(':memory:')conn.execute('''CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT UNIQUE, login_count INTEGER)''')# Первый вызов — вставит новую записьconn.execute('''INSERT INTO users (email, login_count) VALUES ('alice@mail.com', 1) ON CONFLICT(email) DO UPDATE SET login_count = login_count + 1''')# Второй вызов — увеличит счётчикconn.execute('''INSERT INTO users (email, login_count) VALUES ('alice@mail.com', 1) ON CONFLICT(email) DO UPDATE SET login_count = users.login_count + 1''')result = conn.execute('SELECT * FROM users').fetchone()print(result) # (1, 'alice@mail.com', 2)➡️ ON CONFLICT DO UPDATE — магия UPSERT. Ключевое поле должно иметь ограничение UNIQUE или PRIMARY KEY.✅ RETURNING: возвращай изменённые строки сразуРаньше чтобы получить ID новой записи, делали отдельный SELECT. Теперь всё сразу:# Возвращаем ID и данные новой записиcursor = conn.execute('''INSERT INTO users (email, login_count) VALUES ('bob@mail.com', 1) RETURNING id, email''')new_user = cursor.fetchone()print(f'Новый ID: {new_user[0]}, email: {new_user[1]}')# Работает и с UPDATE/DELETEcursor = conn.execute('''UPDATE users SET login_count = login_count * 2 WHERE email = 'alice@mail.com' RETURNING email, login_count''')updated = cursor.fetchone()print(f'Обновле
👩💻 Обработка исключений в Python: try, except, finallyВ этом видео автор подробно разбирает, как предсказуемо управлять ошибками в программах с помощью конструкции try-except. Вы научитесь перехватывать конкретные типы исключений, корректно освобождать ресурсы в блоке finally и создавать собственные классы ошибок для большей ясности кода.👉 Ссылка на первоисточник 🗣️Запомни: грамотная обработка исключений не скрывает проблемы, а делает поведение программы предсказуемым при любых обстоятельствах — это признак зрелого кода.🤩 Pytstart || #Видеокурс
🗜 Сжатие данных на лету: gzip, lzma (работа со сжатыми файлами)Когда логи занимают гигабайты, а дампы БД не помещаются на диск, сжатие становится must-have. Но не нужно сжимать вручную — Python умеет работать со сжатыми файлами напрямую, как с обычными.⚡️ gzip: стандарт для логов и JSONМодуль gzip даёт тот же интерфейс, что и open(), но прозрачно сжимает:import gzipimport json# ПИШЕМ сжатый JSON одной строкойwith gzip.open('data.json.gz', 'wt', encoding='utf-8') as f: json.dump({'users': [{'id': i, 'name': f'user_{i}'} for i in range(10000)]}, f)# Файл data.json.gz весит в 5-10 раз меньше# ЧИТАЕМ как обычный файлwith gzip.open('data.json.gz', 'rt', encoding='utf-8') as f: data = json.load(f)print(f"Загружено {len(data['users'])} пользователей") # 10000➡️ Режимы: 'rt'/'wt' для текста, 'rb'/'wb' для бинарных данных. Сжатие происходит на лету — не нужно держать в памяти весь несжатый файл.✅ lzma: максимальное сжатие (но медленнее)Для архивов, где важна степень сжатия, а не скорость:import lzmaimport pickle# Сохраняем сжатый pickledata = [{'x': i, 'y': i**2} for i in range(100000)]with lzma.open('data.pickle.xz', 'wb') as f: pickle.dump(data, f)print("Файл сохранён с максимальным сжатием")# Загружаем обратноwith lzma.open('data.pickle.xz', 'rb') as f: loaded = pickle.load(f)print(f"Загружено {len(loaded)} объектов")➡️ lzma (формат .xz) сжимает лучше gzip, но работает медленнее. Идеально для долгосрочного хранения дампов.🔍 Сравниваем gzip vs lzma на практикеimport gzip, lzma, osimport random# Генерируем тестовые данные (повторяющиеся строки хорошо сжимаются)text = '\n'.join(str(random.randint(1000, 9999)) for _ in range(50000))# Сохраняем в разных форматахwith open('raw.txt', 'w') as f: f.write(text)with gzip.open('test.gz', 'wt') as f: f.write(text)with lzma.open('test.xz', 'wt') as f: f.write(text)# Сравниваем размерыfor name in ['raw.txt', 'test.gz', 'test.xz']: print(f"{name}: {os.path.getsize(name)} байт")# raw.txt: 345678 байт# test.gz: 4
👩💻 Работа с внешними API на Python: библиотека requestsВ этом видео автор на практике разбирает, как взаимодействовать с внешними сервисами и данными через API. Вы научитесь отправлять GET и POST-запросы, обрабатывать ответы в формате JSON, а также работать с заголовками и параметрами с помощью универсальной библиотеки requests.👉 Ссылка на первоисточник🗣️Запомни: умение работать с API - это ключ к интеграции ваших программ с миром внешних данных и сервисов, от погоды до нейросетей.🤩 Pytstart || #Видеокурс
🤯 Математика с булевыми значениямиА вы знали, что в Python True и False — это на самом деле просто замаскированные 1 и 0? Тип bool наследуется от int.Поэтому такой код абсолютно валиден:x = True + True + 5print(x) # Вывод: 7 (1 + 1 + 5)Или даже так (используем как индекс):options = ["Нет", "Да"]is_agreed = Trueprint(options[is_agreed]) # Вывод: "Да" (потому что options[1])💡 Примечание: в продакшене лучше так не писать, но знать об этом полезно!#python #funfacts #underTheHood
🖥 Именованные кортежиОбычные кортежи (`tuple`) экономят память, но обращаться к данным по индексу — неудобно.point[0] — что это? Координата X? Широта? ID пользователя?Используйте `NamedTuple` из модуля typing (или `collections`). Это как класс, только легче.from typing import NamedTupleclass Point(NamedTuple): x: int y: intp = Point(10, 20)# Теперь можно обращаться по имени!print(p.x, p.y) # 10 20# Но поведение кортежа сохраняетсяprint(p[0]) # 10Это делает код "самодокументируемым". Идеально для возврата нескольких значений из функции.#python #typing #structures #tips
🖥 Ускорение кода в 100 раз одной строкойЕсли у вас есть функция, которая выполняет тяжелые вычисления (или рекурсию) с одними и теми же аргументами, вы можете мгновенно её ускорить.Импортируем декоратор `@lru_cache` из модуля functools.Пример (классический Фибоначчи):from functools import lru_cache@lru_cache(maxsize=None) # <--- Вся магия здесьdef fib(n): if n < 2: return n return fib(n-1) + fib(n-2)print(fib(50))Без кэша: Python будет считать это вечность (миллиарды вызовов).С кэшем: Результат выведется мгновенно.Декоратор запоминает результаты вызова функции. Если функцию вызовут снова с теми же аргументами, Python не будет считать заново, а просто возьмет готовый ответ из памяти.#python #performance #optimization #functools
🖥 Python-фишка: Переворачиваем словарь одной строкойДопустим, у вас есть словарь, и нужно поменять местами ключи и значения (инвертировать его).Исходные данные:currencies = {'USD': 1, 'EUR': 0.9, 'GBP': 0.8}Вместо того чтобы создавать пустой словарь и запускать цикл, используйте Dict Comprehension:# {значение: ключ}inverted = {value: key for key, value in currencies.items()}print(inverted)# {1: 'USD', 0.9: 'EUR', 0.8: 'GBP'}Синтаксис {k: v for ...} работает так же мощно, как и для списков. Можно даже добавить условия:# Оставим только валюты с курсом меньше 1cheap = {k: v for k, v in currencies.items() if v < 1}