Python для начинающих
1.24K subscribers
546 photos
3 videos
232 files
74 links
Python для начинающих
Download Telegram
Как использовать set для удаления дубликатов из списка

Одна из самых частых задач в Python — избавиться от дубликатов в списке. Можно писать циклы, проверять, есть ли элемент в новом списке… а можно использовать встроенный тип set и сделать всё в одну строку.

### Что такое set?

set — это множество: неупорядоченная коллекция уникальных элементов.
Ключевые свойства:

- в set не бывает дубликатов;
- элементы должны быть хешируемыми (числа, строки, кортежи и т.д.);
- порядок не гарантируется.

Создать множество из списка можно так:

numbers = [1, 2, 2, 3, 3, 3]
unique_numbers = set(numbers)
print(unique_numbers) # {1, 2, 3}


Мы просто «заставили» Python выкинуть всё лишнее.

### Возврат к списку без дубликатов

Часто нужен именно список (с индексами и возможностью изменять элементы). Тогда:

numbers = [1, 2, 2, 3, 3, 3]
unique_list = list(set(numbers))
print(unique_list) # порядок не гарантируется


Важно: порядок элементов может измениться. Для чисел это обычно не критично, но для, скажем, событий во времени — уже проблема.

### Как сохранить порядок и убрать дубликаты?

Комбинируем set и цикл:

items = ["a", "b", "a", "c", "b", "d"]
seen = set()
result = []

for item in items:
if item not in seen:
seen.add(item)
result.append(item)

print(result) # ['a', 'b', 'c', 'd']


Здесь:

- seen хранит уже встреченные элементы;
- result — итоговый список без повторов, в исходном порядке.

### Короткая версия через dict.fromkeys

Если важен порядок, но не хочется писать цикл:

items = ["a", "b", "a", "c", "b", "d"]
result = list(dict.fromkeys(items))
print(result) # ['a', 'b', 'c', 'd']


До Python 3.7 порядок ключей в dict официально не гарантировался, но в современных версиях этот трюк работает надёжно.

### Где это полезно?

- очистка списков e-mail адресов от повторов;
- фильтрация тегов;
- удаление дубликатов из данных перед анализом.

Главное: set — быстрый и простой способ получить уникальные значения. Используйте set, когда порядок не важен, и сочетайте его с дополнительной логикой, когда порядок нужно сохранить.
👍3🔥1
Создание простых Webhook-обработчиков с Flask
Создание простых Webhook-обработчиков с Flask

Иногда нужно, чтобы ваш скрипт реагировал на внешние события: оплата на сайте, новый комментарий в соцсети, пуш из GitHub и т.п. Сервисы решают это через webhooks — они отправляют HTTP-запросы (обычно POST) на ваш URL. Ваша задача — принять запрос и что-то сделать.

Самый простой способ в Python — мини-фреймворк Flask.

---

### Минимальный Webhook на Flask

Установим Flask:

pip install flask


Создадим app.py:

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route("/webhook", methods=["POST"])
def webhook_handler():
data = request.get_json(silent=True) or {}
event_type = data.get("event", "unknown")

# Примитивная обработка события
if event_type == "payment_succeeded":
# здесь могла бы быть запись в базу или отправка email
print("Payment succeeded:", data)
else:
print("Unknown event:", data)

return jsonify({"status": "ok"}), 200

if __name__ == "__main__":
app.run(port=5000, debug=True)


Что тут важно:

- methods=["POST"] — принимаем только POST (классический формат webhook’ов).
- request.get_json() — достаем JSON-тело запроса.
- Возвращаем JSON-ответ и HTTP-код 200, чтобы внешний сервис понял, что все прошло успешно.

---

### Как протестировать без реального сервиса

Можно «прикинуться» внешним сервисом с помощью curl:

curl -X POST http://127.0.0.1:5000/webhook \
-H "Content-Type: application/json" \
-d '{"event":"payment_succeeded","amount":1500}'


В консоли сервера вы увидите распечатанный JSON.

---

### Добавляем простую проверку подписи

Реальные сервисы почти всегда подписывают запросы, чтобы никто не мог отправить фальшивый webhook. Простейший вариант — общий секрет и заголовок с HMAC:

import hmac
import hashlib
from flask import abort

SECRET_TOKEN = "super-secret-key"

def verify_signature(raw_body: bytes, received_signature: str) -> bool:
mac = hmac.new(SECRET_TOKEN.encode(), msg=raw_body, digestmod=hashlib.sha256)
expected_sig = mac.hexdigest()
return hmac.compare_digest(expected_sig, received_signature or "")

@app.route("/secure-webhook", methods=["POST"])
def secure_webhook():
raw_body = request.data
signature = request.headers.get("X-Signature")

if not verify_signature(raw_body, signature):
abort(401, description="Invalid signature")

payload = request.get_json(silent=True) or {}
print("Secure event:", payload)
return jsonify({"status": "accepted"}), 200


Ключевые моменты:

- Используем request.data, а не уже разобранный JSON — подпись считается по «сырому» телу.
- hmac.compare_digest защищает от атак по времени сравнения.
- При неверной подписи сразу возвращаем 401.

---

### Зачем это все начинающему?

Webhook-обработчик — это реальная задачка уровня «боевого» кода:

- вы работаете с HTTP, JSON, заголовками и кодами ответа;
- думаете о безопасности (подписи, проверка метода);
- учитесь разделять «прием запроса» и «бизнес-логику».

А Flask позволяет уложить все это в несколько десятков строк понятного кода — отличный старт для вхождения в мир веб-разработки на Python.
👍4
Работа с emoji и вывод цветных иконок в консоль Python
Работа с emoji и цветными иконками в консоли Python

Консоль не обязана быть скучной. Немного emoji и цвета — и ваш скрипт начинает «разговаривать» с пользователем гораздо живее. Разберём три простых приёма: Unicode-emoji, библиотеку emoji и цветной вывод через colorama.

---

## 1. Emoji по‑простому: через Unicode

Многие emoji — это обычные Unicode-символы. Достаточно вставить их прямо в строку:

print("Task completed ")
print("Warning ⚠️")
print("Python is fun 🐍")


Если консоль и шрифт поддерживают Unicode, всё просто заработает.
Иногда полезно использовать кодовую точку:

smile = "\U0001F600"  # 😀
print(f"Hello {smile}")


---

## 2. Библиотека emoji

Когда хочется писать :smile: вместо поиска нужного символа.

Установка:

pip install emoji


Использование:

import emoji

text = emoji.emojize(
"Server status: :green_circle: Running, :red_circle: Stopped",
language="alias"
)
print(text)


Полезные фишки:
- emoji.demojize("Hello 😀")"Hello :grinning_face:"
- Можно хранить «коды» в файлах настроек и подставлять emoji при выводе.

---

## 3. Цветной текст с colorama

colorama кроссплатформенно добавляет цвет в консоль (особенно удобно на Windows).

Установка:

pip install colorama


Минимальный пример:

from colorama import init, Fore, Style

init(autoreset=True)

print(Fore.GREEN + "Success ")
print(Fore.YELLOW + "Loading... ")
print(Fore.RED + "Error ")
print(Style.DIM + "This is a low-priority message")


Ключевые элементы:
- Fore.RED, Fore.GREEN, Fore.YELLOW и др. — цвет текста.
- Style.BRIGHT, Style.DIM, Style.NORMAL — «стиль» текста.
- autoreset=True избавляет от необходимости вручную сбрасывать цвет.

---

## 4. Комбинируем: мини‑панель статуса

from colorama import init, Fore
import emoji
import time

init(autoreset=True)

def show_status(message, ok=True):
icon = ":check_mark_button:" if ok else ":cross_mark:"
icon = emoji.emojize(icon, language="alias")
color = Fore.GREEN if ok else Fore.RED
print(color + icon, message)

show_status("Connecting to database...")
time.sleep(1)
show_status("Connected", ok=True)
show_status("Failed to fetch data", ok=False)


Итог: пара библиотек + Unicode — и ваша консоль превращается из серого лога в удобную и наглядную панель. Это особенно выручает в CLI‑утилитах, мини‑играх и учебных проектах, где хочется мгновенно различать успех, ошибку и предупреждения.
👍3
Как использовать itertools для нового взгляда на циклы и генераторы
Как использовать itertools: новый взгляд на циклы и генераторы

Если обычные циклы for — это отвертка, то модуль itertools — целый чемодан инструментов. Он позволяет писать короче, читать легче и иногда вообще обходиться без явных циклов.

---

## Бесконечные последовательности без боли

from itertools import count, cycle, repeat

for i in count(10, 2): # 10, 12, 14, ...
if i > 20:
break
print(i)


count(start, step) создает бесконечный счетчик. Безопасность — на вас: обязательно ставьте break.

colors = ["red", "green", "blue"]
for c, n in zip(cycle(colors), range(5)):
print(c)


cycle(iterable) зацикливает список. Удобно для повторяющихся паттернов.

for x in repeat("hello", 3):
print(x)


repeat(obj, times) — ленивый клон объекта.

---

## Перебор сочетаний, перестановок и не только

from itertools import permutations, combinations, product

letters = ["a", "b", "c"]

print(list(permutations(letters, 2))) # Порядок важен
print(list(combinations(letters, 2))) # Порядок не важен


Такие функции идеально подходят для перебора вариантов в брутфорс-задачах, тестировании, комбинаторике.

Декартово произведение:

sizes = ["S", "M"]
colors = ["black", "white"]

for combo in product(sizes, colors):
print(combo)


Это мощнее вложенных циклов и проще читается.

---

## Умная фильтрация и группировка

from itertools import compress

data = [10, 20, 30, 40]
selectors = [True, False, True, False]

print(list(compress(data, selectors))) # [10, 30]


compress фильтрует элементы по маске логических значений.

Группировка подряд идущих одинаковых элементов:

from itertools import groupby

data = "aaabbccccd"

for char, group in groupby(data):
print(char, len(list(group)))
# a 3, b 2, c 4, d 1


Полезно для простого сжатия или анализа последовательностей.

---

## Комбинирование итераторов как конструктор

from itertools import chain, islice

data1 = [1, 2]
data2 = [3, 4, 5]

merged = chain(data1, data2) # ленивое "склеивание"
print(list(islice(merged, 0, 4))) # берём только первые 4


chain объединяет несколько источников, а islice позволяет "отрезать" нужный кусок без создания лишних списков.

---

itertools — про ленивость и композицию. Вместо того чтобы писать сложные циклы, вы собираете поведение из маленьких строительных блоков. Результат — короче код, меньше ошибок и приятное ощущение "Python-стиля".
👍3
Работа с collections: полезные структуры данных и где их использовать
Работа с collections: полезные структуры данных и где их использовать

Стандартные списки и словари — отличная вещь, но модуль collections превращает их в инструменты «профессионального уровня». Разберём ключевые структуры, которые реально упрощают код.

---

### Counter: считаем всё подряд

Counter — словарь для подсчёта объектов.

from collections import Counter

text = "banana bandana"
counter = Counter(text)

print(counter) # сколько раз встречается каждый символ
print(counter.most_common(2)) # два самых частых


Где полезно: подсчёт частоты слов в тексте, статистика действий пользователей, анализ логов.

---

### defaultdict: словарь, который не ругается на новые ключи

Обычный словарь бросит KeyError, если ключа нет. defaultdict автоматически создаёт значение по умолчанию.

from collections import defaultdict

groups = defaultdict(list)

data = [("cat", 1), ("dog", 2), ("cat", 3)]
for animal, value in data:
groups[animal].append(value)

print(groups) # {'cat': [1, 3], 'dog': [2]}


Где полезно: группировка данных, инвертирование словарей, построение графов (списки соседей).

---

### namedtuple: читаемые «кортежи с именами»

Обычный кортеж непонятен: user[0], user[1]namedtuple даёт имена полям.

from collections import namedtuple

User = namedtuple("User", ["name", "age"])
user = User(name="Alice", age=30)

print(user.name)
print(user.age)


Где полезно: лёгкие «объекты без классов» — координаты, настройки, параметры функций. Иммутабельность помогает избежать случайных изменений.

---

### deque: очередь и стек без тормозов

Список плохо работает с pop(0) — это O(n). deque оптимизирован для добавления/удаления с обоих концов.

from collections import deque

queue = deque()

queue.append("task1")
queue.append("task2")
queue.appendleft("urgent")

print(queue.popleft()) # 'urgent'
print(queue.pop()) # 'task2'


Где полезно: очереди задач, реализовать стек/очередь, «скользящее окно» последних N элементов.

---

### OrderedDict: порядок тоже важен

С Python 3.7 обычный dict уже запоминает порядок вставки, но OrderedDict всё ещё полезен там, где нужны дополнительные операции (например, move_to_end). В современных проектах используют реже, но стоит знать о его существовании.

---

Модуль collections — это набор готовых решений для типичных задач работы с данными. Зная эти структуры, вы часто пишете меньше кода, делаете его быстрее и понятнее — и меньше изобретаете велосипеды.
👍4
Создание генераторов: экономим память при обработке больших данных
Создание генераторов: экономим память при обработке больших данных

Представьте, что вам нужно обработать файл на 10 ГБ, посчитать суммы, отфильтровать строки, что‑то преобразовать. Если пытаться «затащить» всё в память списками — привет, MemoryError. Здесь на сцену выходят генераторы.

Генератор — это «ленивый» источник данных: он отдаёт элементы по одному, по запросу, не храня весь результат сразу. Именно поэтому генераторы так хорошо подходят для больших данных и потоковой обработки.

### Генераторные выражения

Список:

nums = [i * 2 for i in range(10_000_000)]


держит в памяти все 10 млн элементов.

Генератор:

nums_gen = (i * 2 for i in range(10_000_000))


хранит только текущее состояние итерации. Память почти не растёт, пока вы не начинаете обходить nums_gen:

total = 0
for value in nums_gen:
total += value


### Собственные генераторные функции

Ключевое слово yield превращает функцию в генератор:

def read_large_file(path):
with open(path, 'r', encoding='utf-8') as f:
for line in f:
yield line.strip()


Здесь не создаётся список строк файла — каждая строка обрабатывается по мере чтения:

def iter_error_lines(path):
for line in read_large_file(path):
if 'ERROR' in line:
yield line

for err_line in iter_error_lines('app.log'):
print(err_line)


Файл может быть гигантским — программа использует почти столько же памяти, как и при чтении маленького файла.

### Конвейер из генераторов

Самое интересное начинается, когда вы соединяете генераторы в цепочку — получается «конвейер»:

def iter_numbers(path):
for line in read_large_file(path):
if line and line[0].isdigit():
yield int(line)

def filter_even(seq):
for n in seq:
if n % 2 == 0:
yield n

def sum_limited(seq, limit):
total = 0
for n in seq:
total += n
if total > limit:
break
return total

numbers = iter_numbers('numbers.txt')
even_numbers = filter_even(numbers)
result = sum_limited(even_numbers, limit=1_000_000)
print(result)


Каждый шаг обрабатывает элементы по одному: прочитали строку → превратили в число → отфильтровали → учли в сумме. Никаких огромных временных списков.

### Когда использовать генераторы

- Обработка больших файлов и потоков данных.
- Длинные вычисления, результат которых нужен по частям.
- Организация удобных «ленивых» API (итераторы вместо списков).

Генераторы — это простой способ сделать код и аккуратнее, и экономичнее по памяти, не усложняя архитектуру. Если вы уже пишете списковые включения, вы почти готовы использовать генераторы — достаточно заменить [] на ().
👍1🔥1
Как применять enum для улучшения читаемости кода
Python для начинающих: как enum делает код понятнее

Когда в коде появляются «магические числа» и странные строки, читаемость падает. Встречали что‑то вроде:

if status == 3:
send_email()


Через неделю уже непонятно, что такое 3. Ошибка не в логике — ошибка в обозначениях. Здесь на сцену выходит модуль enum.

---

## Что такое Enum?

Enum (перечисление) — это способ задать набор именованных констант. Вместо чисел и строк, разбросанных по коду, вы используете говорящие имена.

from enum import Enum

class OrderStatus(Enum):
NEW = 1
PAID = 2
SHIPPED = 3
CANCELED = 4


Теперь вместо 3 у вас есть OrderStatus.SHIPPED. Код сразу объясняет сам себя.

---

## Пример: читаемый if

До:

def handle_order(status):
if status == 1:
print("Create invoice")
elif status == 2:
print("Prepare shipment")
elif status == 3:
print("Send tracking code")


После:

from enum import Enum

class OrderStatus(Enum):
NEW = 1
PAID = 2
SHIPPED = 3

def handle_order(status: OrderStatus):
if status is OrderStatus.NEW:
print("Create invoice")
elif status is OrderStatus.PAID:
print("Prepare shipment")
elif status is OrderStatus.SHIPPED:
print("Send tracking code")


Что улучшилось:

- код сам документирует возможные статусы;
- меньше шансов перепутать значения;
- IDE подсказывает варианты (OrderStatus. → список).

---

## Enum вместо строк

Частая ловушка — сравнение со строками:

if role == "admin":
...


Опечатка — и проверка ломается. С Enum:

from enum import Enum, auto

class UserRole(Enum):
ADMIN = auto()
EDITOR = auto()
VIEWER = auto()

def can_delete(user_role: UserRole) -> bool:
return user_role is UserRole.ADMIN


auto() сам проставит уникальные значения — нас интересуют не числа, а имена.

---

## Небольшой бонус: словари с Enum

Enum удобно использовать как ключи:

from enum import Enum, auto

class LogLevel(Enum):
DEBUG = auto()
INFO = auto()
ERROR = auto()

LOG_PREFIXES = {
LogLevel.DEBUG: "[DEBUG]",
LogLevel.INFO: "[INFO]",
LogLevel.ERROR: "[ERROR]",
}

def log(level: LogLevel, message: str) -> None:
prefix = LOG_PREFIXES[level]
print(prefix, message)


Здесь невозможно случайно обратиться к "DBG" вместо "DEBUG" — только валидные значения перечисления.

---

Enum — это маленькое улучшение, которое сильно поднимает читаемость и надежность кода. Как только в вашем проекте появляется набор фиксированных состояний, ролей, типов — это сигнал: пора завести перечисление.
👍2
Подключение и использование конфигурационных файлов с configparser
Python для начинающих: configparser — приручаем конфиги как профи

Жёстко прописывать настройки прямо в коде — плохая идея. Захочется сменить пароль к базе или лог‑уровень — и вы уже лазите по файлам Python, рискуя всё сломать. Гораздо удобнее хранить настройки в конфигурационных файлах. Для этого в стандартной библиотеке есть модуль configparser.

---

### Как выглядит конфиг

Создадим файл settings.ini:

[database]
host = localhost
port = 5432
user = admin
password = secret

[logging]
level = DEBUG
file = app.log


Структура проста: секции в квадратных скобках и пары ключ = значение.

---

### Чтение конфигурации

from configparser import ConfigParser

config = ConfigParser()
config.read("settings.ini")

db_host = config["database"]["host"]
db_port = config.getint("database", "port")
log_level = config.get("logging", "level", fallback="INFO")

print(db_host, db_port, log_level)


Ключевые моменты:

- config["section"]["option"] — базовый доступ.
- getint, getfloat, getboolean — сразу приводят к нужному типу.
- fallback спасает, если ключа нет.

---

### Изменение и сохранение конфига

from configparser import ConfigParser

config = ConfigParser()
config.read("settings.ini")

config["database"]["host"] = "db.mycompany.com"

if "feature_flags" not in config:
config["feature_flags"] = {}
config["feature_flags"]["new_ui"] = "true"

with open("settings.ini", "w") as configfile:
config.write(configfile)


Так можно не только читать, но и аккуратно обновлять настройки.

---

### Конфиг по умолчанию

Иногда нужно задать значения «на всякий случай»:

from configparser import ConfigParser

config = ConfigParser(
defaults={
"level": "INFO",
"file": "app.log"
}
)

config.read("settings.ini")

log_level = config["logging"]["level"] # если нет в секции, возьмёт из defaults
log_file = config["logging"]["file"]


---

### Когда это полезно

- разные настройки для dev / prod без изменения кода;
- хранение секретов вне репозитория (через отдельный local_settings.ini);
- быстрое переключение фич через флаги.

configparser — простой, но мощный способ отделить код от настроек. Один раз настроили структуру .ini — и дальше ваш код становится гораздо чище и гибче.
👍1
Использование pathlib для работы с путями и файлами
Python для начинающих: как pathlib спасет вас от хаоса с путями

Если вы хоть раз писали что-то вроде:

file_path = "C:\\Users\\user\\projects\\data\\file.txt"


и ловили ошибки из‑за слэшей и кодировок, то модуль pathlib — ваш новый лучший друг. Он превращает работу с путями и файлами в аккуратные объектные операции, вместо бесконечной возни со строками.

---

### Базовая идея: Path вместо строк

Главный герой — класс Path:

from pathlib import Path

base_dir = Path.home() / "projects" / "demo"
print(base_dir)


Оператор / здесь не делит, а красиво склеивает части пути, независимо от ОС (Windows, Linux, macOS). Никаких os.path.join, никаких ручных слэшей.

---

### Проверка существования и типа

pathlib сразу умеет отвечать на важные вопросы:

from pathlib import Path

path = Path("data/example.txt")

print(path.exists()) # файл или папка есть?
print(path.is_file()) # это файл?
print(path.is_dir()) # это папка?


Можно безопасно проверять перед чтением или удалением.

---

### Чтение и запись файлов

Чтение и запись — это методы объекта пути:

from pathlib import Path

file_path = Path("data/notes.txt")

# запись текста
file_path.write_text("Hello, pathlib!", encoding="utf-8")

# чтение текста
content = file_path.read_text(encoding="utf-8")
print(content)


Не нужно вручную открывать и закрывать файлы — Path делает это за вас.

---

### Создание папок и обход директорий

Создать дерево каталогов и пройтись по файлам — одна строка:

from pathlib import Path

base = Path("logs/app")

# создаем все недостающие каталоги
base.mkdir(parents=True, exist_ok=True)

# ищем все .log файлы в подпапках
for log_file in base.rglob("*.log"):
print(log_file.name, log_file.stat().st_size, "bytes")


Метод rglob рекурсивно ищет файлы по шаблону, stat() дает информацию о файле.

---

### Работа с именами, расширениями и родителями

Разбирать путь по частям стало легко:

from pathlib import Path

path = Path("data/archive/report_2024.csv")

print(path.name) # report_2024.csv
print(path.stem) # report_2024
print(path.suffix) # .csv
print(path.parent) # data/archive

new_path = path.with_suffix(".xlsx")
print(new_path) # data/archive/report_2024.xlsx


---

### Почему стоит перейти на pathlib уже сейчас

- Кроссплатформенно: один и тот же код под Windows и Linux.
- Читаемо: Path‑объекты ведут себя как реальные объекты, а не как странные строки.
- Богатый функционал: от обхода директорий до смены расширений.

Если вы еще работаете с путями как со строками — попробуйте переписать небольшой скрипт на pathlib. Скорее всего, назад уже не захочется.
🔥2
Создание простых HTTP-запросов вручную с модулем http.client
Создание простых HTTP-запросов вручную с модулем http.client

Большинство новичков начинают работу с сетью через библиотеку requests. Она удобная и «магическая». Но если хочется понять, что реально происходит под капотом, полезно познакомиться с модулем стандартной библиотеки http.client.

http.client работает на более низком уровне: вы сами открываете соединение, отправляете строку запроса, заголовки, читаете ответ. Немного «олдскул», зато отлично прокачивает понимание HTTP.

---

### Простой GET-запрос

Сделаем обычный GET-запрос к публичному API:

import http.client
import json

conn = http.client.HTTPSConnection("api.github.com")

headers = {
"User-Agent": "python-http.client-demo",
"Accept": "application/vnd.github.v3+json"
}

conn.request("GET", "/repos/python/cpython", headers=headers)
response = conn.getresponse()

print("Status:", response.status, response.reason)

data = response.read()
payload = json.loads(data)

print("Full name:", payload["full_name"])
print("Stars:", payload["stargazers_count"])

conn.close()


Что здесь важно:

- HTTPSConnection — шифрованное соединение (TLS).
- request(method, url, body=None, headers={}) — отправка запроса.
- getresponse() — возвращает объект ответа.
- status, reason, read() — статус-код, текстовое описание и тело ответа.

Без обязательного заголовка User-Agent GitHub может ответить ошибкой — это хороший пример, почему заголовки критичны.

---

### Отправка POST-запроса с данными

Теперь отправим POST с JSON-данными на тестовый сервис httpbin.org:

import http.client
import json

conn = http.client.HTTPSConnection("httpbin.org")

payload = {"name": "Alice", "lang": "Python"}
body = json.dumps(payload)

headers = {
"Content-Type": "application/json",
"Content-Length": str(len(body))
}

conn.request("POST", "/post", body=body, headers=headers)
response = conn.getresponse()

print("Status:", response.status)
response_data = response.read().decode("utf-8")

print("Response body:", response_data[:200], "...")
conn.close()


Здесь уже видно «ручную работу»:

- Сериализуем данные сами (json.dumps).
- Указываем Content-Type и Content-Length вручную.
- Сами кодируем/декодируем текст (decode("utf-8")).

---

### Зачем это нужно?

- Понимание того, как устроен HTTP «на проводе».
- Возможность тонко контролировать заголовки и поведение соединения.
- Умение работать только со стандартной библиотекой, без внешних зависимостей.

После таких упражнений любые высокоуровневые библиотеки кажутся гораздо понятнее: вы уже знаете, какую именно «рутину» они берут на себя.
👍2
Понимание работы контекстных менеджеров и создание своих с помощью contextlib
Понимание работы контекстных менеджеров и создание своих с помощью contextlib

Если вы когда‑нибудь писали:

with open("data.txt") as f:
content = f.read()


то уже пользовались контекстным менеджером — просто, возможно, не задумывались об этом. Давайте разберёмся, что там происходит под капотом и как писать свои.

---

### Что такое контекстный менеджер?

Контекстный менеджер — это объект, который знает, что делать до и после блока with.

Под капотом Python делает примерно так:

manager = open("data.txt")
f = manager.__enter__()
try:
content = f.read()
finally:
manager.__exit__(None, None, None)


Два ключевых метода:

- __enter__(self) — подготавливает ресурс, возвращает объект, который попадёт в as.
- __exit__(self, exc_type, exc_val, exc_tb) — освобождает ресурс, даже если возникло исключение.

---

### Пишем свой контекстный менеджер “вручную”

Простой пример — измерение времени работы блока кода:

import time

class Timer:
def __enter__(self):
self.start = time.perf_counter()
return self

def __exit__(self, exc_type, exc_val, exc_tb):
self.end = time.perf_counter()
self.elapsed = self.end - self.start
print(f"Elapsed: {self.elapsed:.4f} seconds")

with Timer() as t:
total = sum(range(1_000_000))


Плюсы: полный контроль, можно хранить состояние в атрибутах объекта. Минус: много шаблонного кода.

---

### contextlib.contextmanager: контекстный менеджер из функции

Модуль contextlib позволяет писать их короче, с помощью генераторов:

from contextlib import contextmanager

@contextmanager
def open_file(path, mode="r"):
f = open(path, mode)
try:
yield f # то, что вернётся в "as"
finally:
f.close() # выполнится всегда

with open_file("data.txt") as f:
data = f.read()


Всё, что до yield, — это логика __enter__, а всё после — __exit__.

---

### Практический пример: временная смена текущей директории

import os
from contextlib import contextmanager

@contextmanager
def change_dir(path):
old_dir = os.getcwd()
os.chdir(path)
try:
yield
finally:
os.chdir(old_dir)

print(os.getcwd())
with change_dir("/tmp"):
print(os.getcwd())
print(os.getcwd())


Контекст гарантирует, что директория вернётся к исходной даже при ошибках внутри блока with.

---

### Когда стоит использовать контекстные менеджеры

- работа с файлами и сетевыми соединениями;
- блокировки потоков и транзакции в БД;
- временные настройки (логирование, окружение, директории);
- любые ресурсы, которые надо обязательно освободить.

Контекстный менеджер — это способ формально описать жизненный цикл ресурса: “взял → поработал → убрал за собой”, и contextlib делает это описание максимально коротким и наглядным.
🔥2👍1