Введение в работу с protobuf: быстрая сериализация данных
JSON удобен, пока данные небольшие и требования к скорости умеренные. Но как только вы начинаете передавать тысячи сообщений в секунду между сервисами, JSON внезапно становится «тормозом». Здесь на сцену выходит Protocol Buffers (protobuf) — бинарный формат от Google, заточенный под скорость и компактность.
---
### Что такое protobuf в двух словах
Protobuf — это:
- бинарный формат сериализации (данные занимают меньше места, чем JSON);
- строгая схема (типизация, обязательные/необязательные поля);
- кросс-языковая поддержка (Python, Go, Java, C++ и т.д.).
Сначала вы описываете структуру данных в
---
### Описание схемы
Создадим файл
Ключевые моменты:
-
-
- Числа
---
### Генерация Python-классов
Устанавливаем пакет:
Компилируем схему (нужен установленный
Появится
---
### Сериализация и десериализация
По сравнению с JSON:
- меньше размер (особенно для больших структур и списков);
- быстрая (де)сериализация;
- гарантируется наличие нужных полей и их типов.
---
### Эволюция схемы без боли
Сильная сторона protobuf — обратная совместимость.
Вы можете:
- добавлять новые поля в конец (
- помечать поля как устаревшие, но пока не удалять их.
Старый клиент просто игнорирует незнакомые теги, а новый — использует дополнительные поля, если они есть.
---
### Когда protobuf действительно нужен
Используйте protobuf, если:
- у вас есть микросервисы, которым нужно быстро обмениваться структурированными данными;
- важна экономия трафика;
- требуется строгая и эволюционирующая схема данных.
Если же вы просто сохраняете настройки в файл или делаете маленький скрипт-утилиту, JSON остаётся проще. Но как только проект растёт — protobuf становится важным инструментом Python-разработчика.
JSON удобен, пока данные небольшие и требования к скорости умеренные. Но как только вы начинаете передавать тысячи сообщений в секунду между сервисами, JSON внезапно становится «тормозом». Здесь на сцену выходит Protocol Buffers (protobuf) — бинарный формат от Google, заточенный под скорость и компактность.
---
### Что такое protobuf в двух словах
Protobuf — это:
- бинарный формат сериализации (данные занимают меньше места, чем JSON);
- строгая схема (типизация, обязательные/необязательные поля);
- кросс-языковая поддержка (Python, Go, Java, C++ и т.д.).
Сначала вы описываете структуру данных в
.proto файле, затем компилируете его, и уже сгенерированный Python-код используете как обычные классы.---
### Описание схемы
Создадим файл
user.proto:syntax = "proto3";
message User {
int32 id = 1;
string name = 2;
string email = 3;
repeated string tags = 4;
}
Ключевые моменты:
-
message — аналог класса/структуры.-
repeated — список значений.- Числа
= 1, = 2 — теги полей. Они нужны для бинарного формата и обратной совместимости.---
### Генерация Python-классов
Устанавливаем пакет:
pip install protobuf
Компилируем схему (нужен установленный
protoc):protoc --python_out=. user.proto
Появится
user_pb2.py — не редактируем его вручную, просто используем.---
### Сериализация и десериализация
from user_pb2 import User
def create_user() -> User:
user = User(
id=1,
name="Alice",
email="alice@example.com",
tags=["admin", "premium"]
)
return user
user = create_user()
# сериализация в бинарный формат
data_bytes = user.SerializeToString()
# восстановление объекта из байт
user_copy = User()
user_copy.ParseFromString(data_bytes)
print(len(data_bytes)) # компактный размер
print(user_copy.name) # Alice
print(user_copy.tags) # ['admin', 'premium']
По сравнению с JSON:
- меньше размер (особенно для больших структур и списков);
- быстрая (де)сериализация;
- гарантируется наличие нужных полей и их типов.
---
### Эволюция схемы без боли
Сильная сторона protobuf — обратная совместимость.
Вы можете:
- добавлять новые поля в конец (
= 5, = 6), не ломая старые клиенты;- помечать поля как устаревшие, но пока не удалять их.
Старый клиент просто игнорирует незнакомые теги, а новый — использует дополнительные поля, если они есть.
---
### Когда protobuf действительно нужен
Используйте protobuf, если:
- у вас есть микросервисы, которым нужно быстро обмениваться структурированными данными;
- важна экономия трафика;
- требуется строгая и эволюционирующая схема данных.
Если же вы просто сохраняете настройки в файл или делаете маленький скрипт-утилиту, JSON остаётся проще. Но как только проект растёт — protobuf становится важным инструментом Python-разработчика.
👍3
Создание интерактивных терминальных интерфейсов с Prompt Toolkit
Большинство начинающих знакомится с Python через
Это мощный конструктор для создания интерактивных CLI-приложений: от умных консолей до мини‑IDE прямо в терминале.
---
### Установка
---
### Пример 1: улучшенный
Что здесь полезного:
- автодополнение по Tab из списка
- история введённых команд,
- редактирование строки как в нормальных шеллах (стрелки, Home/End и т.д.) — «из коробки».
---
### Пример 2: подсветка синтаксиса
Можно превратить ввод в мини‑редактор с подсветкой кода:
Особенности:
-
-
---
### Пример 3: валидация ввода на лету
Плюсы:
- пользователь сразу видит ошибку (сообщение + позиция курсора),
- валидатор легко заменить любым своим (email, путь к файлу, диапазоны и т.д.).
---
### Зачем это всё новичку?
Prompt Toolkit даёт быстрый путь от «скучного консольного ввода» к удобным инструментам:
- свои REPL‑консоли,
- интерактивные помощники и установщики,
- учебные мини‑шеллы для отладки идей.
Один модуль — и ваш терминал перестаёт быть просто чёрным окном, а превращается в полноценный интерфейс для пользователя.
Большинство начинающих знакомится с Python через
input() и print(). Но как только хочется чего-то «живого» в терминале — история команд, подсветка, автодополнение — стандартных средств уже мало. Здесь на сцену выходит библиотека prompt_toolkit.Это мощный конструктор для создания интерактивных CLI-приложений: от умных консолей до мини‑IDE прямо в терминале.
---
### Установка
pip install prompt_toolkit
---
### Пример 1: улучшенный
input() с историей и автодополнениемfrom prompt_toolkit import prompt
from prompt_toolkit.completion import WordCompleter
commands = ["start", "stop", "status", "restart"]
command_completer = WordCompleter(commands, ignore_case=True)
while True:
user_input = prompt("cmd> ", completer=command_completer)
if user_input == "exit":
break
print(f"You entered: {user_input}")
Что здесь полезного:
- автодополнение по Tab из списка
commands,- история введённых команд,
- редактирование строки как в нормальных шеллах (стрелки, Home/End и т.д.) — «из коробки».
---
### Пример 2: подсветка синтаксиса
Можно превратить ввод в мини‑редактор с подсветкой кода:
from prompt_toolkit import prompt
from prompt_toolkit.lexers import PygmentsLexer
from pygments.lexers import PythonLexer
code = prompt(
"Enter Python code:\n",
lexer=PygmentsLexer(PythonLexer),
multiline=True
)
print("You wrote:")
print(code)
Особенности:
-
multiline=True позволяет писать блок кода (несколько строк).-
PygmentsLexer + PythonLexer дают подсветку синтаксиса прямо при вводе.---
### Пример 3: валидация ввода на лету
from prompt_toolkit import prompt
from prompt_toolkit.validation import Validator, ValidationError
class IntValidator(Validator):
def validate(self, document):
text = document.text
if not text.isdigit():
raise ValidationError(
message="Please enter an integer",
cursor_position=len(text)
)
age = prompt("Enter your age: ", validator=IntValidator(), validate_while_typing=True)
print(f"Age: {age}")
Плюсы:
- пользователь сразу видит ошибку (сообщение + позиция курсора),
- валидатор легко заменить любым своим (email, путь к файлу, диапазоны и т.д.).
---
### Зачем это всё новичку?
Prompt Toolkit даёт быстрый путь от «скучного консольного ввода» к удобным инструментам:
- свои REPL‑консоли,
- интерактивные помощники и установщики,
- учебные мини‑шеллы для отладки идей.
Один модуль — и ваш терминал перестаёт быть просто чёрным окном, а превращается в полноценный интерфейс для пользователя.
👍4
Как использовать dataclass и не утонуть в конструкторе
Если вы когда‑нибудь писали класс с десятком полей и бесконечным
---
## Что такое dataclass?
-
-
-
- и другие полезные методы
Вместо громоздкого класса:
Можно написать:
Dataclass сгенерирует
---
## Поля по умолчанию и field()
Нужно задать сложное значение по умолчанию (например, список)? Делать
---
## Замороженные dataclass’ы (иммутабельные объекты)
Хотите, чтобы объект нельзя было менять после создания? Используйте
Это удобно для «чистых» структур данных: координат, конфигураций, ключей в словарях.
---
## Сравнение объектов
Обычные классы сравниваются по идентичности (это один и тот же объект или нет). Dataclass по умолчанию сравнивает значения полей:
---
## Кратко о плюсах dataclass
- Меньше шаблонного кода (
- Читаемые и предсказуемые классы данных
- Простая работа со значениями по умолчанию
- Поддержка типов (аннотации обязательны и делают код понятнее)
- Возможность делать объекты иммутабельными
Если вы когда‑нибудь писали класс с десятком полей и бесконечным
__init__, то модуль dataclasses — это то, что вы искали. Он появился в Python 3.7 и превращает «болванки данных» в аккуратные, удобные структуры без тонны шаблонного кода.---
## Что такое dataclass?
dataclass — это декоратор, который автоматически генерирует:-
__init__-
__repr__-
__eq__ (сравнение объектов)- и другие полезные методы
Вместо громоздкого класса:
class User:
def __init__(self, name: str, age: int, active: bool = True):
self.name = name
self.age = age
self.active = active
def __repr__(self):
return f"User(name={self.name!r}, age={self.age!r}, active={self.active!r})"
Можно написать:
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
active: bool = True
user = User("Alice", 25)
print(user)
Dataclass сгенерирует
__init__ и красивый __repr__ сам.---
## Поля по умолчанию и field()
Нужно задать сложное значение по умолчанию (например, список)? Делать
friends = [] в определении класса опасно — список будет общим для всех экземпляров. В dataclasses это решается через field:from dataclasses import dataclass, field
from typing import List
@dataclass
class ChatRoom:
title: str
members: List[str] = field(default_factory=list)
room = ChatRoom("Python Room")
room.members.append("Alice")
room2 = ChatRoom("Another Room")
print(room.members) # ['Alice']
print(room2.members) # []
default_factory создает новое значение для каждого объекта.---
## Замороженные dataclass’ы (иммутабельные объекты)
Хотите, чтобы объект нельзя было менять после создания? Используйте
frozen=True:from dataclasses import dataclass
@dataclass(frozen=True)
class Point:
x: float
y: float
p = Point(1, 2)
# p.x = 10 # вызовет ошибку FrozenInstanceError
Это удобно для «чистых» структур данных: координат, конфигураций, ключей в словарях.
---
## Сравнение объектов
Обычные классы сравниваются по идентичности (это один и тот же объект или нет). Dataclass по умолчанию сравнивает значения полей:
from dataclasses import dataclass
@dataclass
class Product:
name: str
price: float
p1 = Product("Book", 10.0)
p2 = Product("Book", 10.0)
print(p1 == p2) # True
---
## Кратко о плюсах dataclass
- Меньше шаблонного кода (
__init__, __repr__, __eq__ и т.д.)- Читаемые и предсказуемые классы данных
- Простая работа со значениями по умолчанию
- Поддержка типов (аннотации обязательны и делают код понятнее)
- Возможность делать объекты иммутабельными
dataclass — идеальный инструмент, когда вам нужен «умный словарь» с атрибутами и типами, но без лишней головной боли.👍4🔥1
Работа с фоновыми задачами в Python с помощью
Иногда программе нужно делать сразу несколько вещей: скачивать данные, обрабатывать файлы, показывать прогресс пользователю. Делать это строго по очереди — значит тормозить. Здесь на сцену выходит модуль
### Простейший поток
Запустим функцию в фоне, пока основная программа живет своей жизнью:
### Несколько фоновых задач
Часто нужно запустить не одну, а сразу пачку задач:
Файлы «скачиваются» параллельно: пока один ждет сеть, другой может работать.
### Демон-потоки: тихие работяги
Иногда нужно, чтобы поток не блокировал завершение программы (логирование, метрики):
Если
### Безопасный доступ к общим данным
Главная ловушка многопоточности — гонки данных. Два потока меняют одну переменную — результат непредсказуем. Используем
---
threadingИногда программе нужно делать сразу несколько вещей: скачивать данные, обрабатывать файлы, показывать прогресс пользователю. Делать это строго по очереди — значит тормозить. Здесь на сцену выходит модуль
threading, позволяющий запускать задачи в отдельных потоках.### Простейший поток
Запустим функцию в фоне, пока основная программа живет своей жизнью:
import threading
import time
def background_task(name, delay):
for i in range(3):
time.sleep(delay)
print(f"[{name}] step {i}")
t = threading.Thread(target=background_task, args=("worker-1", 1))
t.start()
print("Main thread continues...")
t.join() # ждем завершения потока
print("Done")
Thread получает функцию target и аргументы args. start() запускает поток, join() — блокирует основной поток до его завершения.### Несколько фоновых задач
Часто нужно запустить не одну, а сразу пачку задач:
def download_file(file_id):
print(f"Start downloading {file_id}")
time.sleep(2)
print(f"Finished {file_id}")
threads = []
for i in range(5):
t = threading.Thread(target=download_file, args=(f"file_{i}",))
t.start()
threads.append(t)
for t in threads:
t.join()
print("All downloads finished")
Файлы «скачиваются» параллельно: пока один ждет сеть, другой может работать.
### Демон-потоки: тихие работяги
Иногда нужно, чтобы поток не блокировал завершение программы (логирование, метрики):
def logger():
while True:
time.sleep(1)
print("logging...")
log_thread = threading.Thread(target=logger, daemon=True)
log_thread.start()
time.sleep(3)
print("Main thread exits") # демон-поток автоматически завершится
Если
daemon=True, поток живет, пока жив основной процесс.### Безопасный доступ к общим данным
Главная ловушка многопоточности — гонки данных. Два потока меняют одну переменную — результат непредсказуем. Используем
Lock:counter = 0
lock = threading.Lock()
def increment(n):
global counter
for _ in range(n):
with lock:
counter += 1
threads = []
for _ in range(5):
t = threading.Thread(target=increment, args=(100000,))
t.start()
threads.append(t)
for t in threads:
t.join()
print("Counter:", counter)
with lock: гарантирует, что только один поток за раз меняет counter.---
threading полезен, когда задачи много ждут (сеть, диск, ввод-вывод). Для тяжелых вычислений лучше посмотреть в сторону multiprocessing, но для фоновых задач, таймеров, сервисных процессов threading — быстрый и удобный инструмент, который стоит освоить одним из первых.👍4
Изучение io.StringIO и BytesIO: виртуальные файлы в Python
Иногда файл нужен… но создавать его на диске совсем не хочется. Лишние записи на диск, временные файлы, уборка за собой — все это раздражает. Именно тут в игру вступают
### Когда это полезно
- тестировать код, который работает с файлами, без реальных файлов;
- временно преобразовывать данные (текст или байты);
- имитировать чтение/запись файлов в библиотеках;
- экономить диск и ускорять операции, когда данные краткоживущие.
---
## StringIO: файл из строки
Мы передали в функцию объект, который выглядит как файл: у него есть методы
StringIO можно использовать и для записи:
---
## BytesIO: файл из байтов
Можно симулировать, например, загрузку файла:
---
## Полезные мелочи
- У обоих есть
- Работают с
- Отлично подходят для unit-тестов: вместо реального файла подсовываете
Иногда файл нужен… но создавать его на диске совсем не хочется. Лишние записи на диск, временные файлы, уборка за собой — все это раздражает. Именно тут в игру вступают
io.StringIO и io.BytesIO — виртуальные файлы в памяти.### Когда это полезно
- тестировать код, который работает с файлами, без реальных файлов;
- временно преобразовывать данные (текст или байты);
- имитировать чтение/запись файлов в библиотеках;
- экономить диск и ускорять операции, когда данные краткоживущие.
---
## StringIO: файл из строки
StringIO работает с текстом (str) как обычный файловый объект.from io import StringIO
def process_stream(stream):
result = []
for line in stream:
line = line.strip()
if line:
result.append(line.upper())
return result
data = "hello\n\nworld\npython\n"
fake_file = StringIO(data)
processed = process_stream(fake_file)
print(processed) # ['HELLO', 'WORLD', 'PYTHON']
Мы передали в функцию объект, который выглядит как файл: у него есть методы
read(), write(), он итерируем по строкам. Но на диске ничего не лежит — все в памяти.StringIO можно использовать и для записи:
from io import StringIO
buffer = StringIO()
buffer.write("line 1\n")
buffer.write("line 2\n")
content = buffer.getvalue()
print(content)
getvalue() — ключевой метод: он возвращает все накопленные данные.---
## BytesIO: файл из байтов
BytesIO делает то же самое, но для байтов (bytes). Это удобно, когда вы работаете с двоичными форматами, изображениями или сетевыми данными.from io import BytesIO
raw_data = b"\x50\x59\x54\x48\x4f\x4e" # 'PYTHON' в ASCII
byte_stream = BytesIO(raw_data)
chunk = byte_stream.read(3)
print(chunk) # b'PYT'
print(byte_stream.read()) # b'HON'
Можно симулировать, например, загрузку файла:
from io import BytesIO
def fake_download():
return BytesIO(b"PNGDATA...")
stream = fake_download()
data = stream.read()
print(len(data))
---
## Полезные мелочи
- У обоих есть
.seek() и .tell() — для перемещения по «файлу».- Работают с
with как обычные файлы.- Отлично подходят для unit-тестов: вместо реального файла подсовываете
StringIO/BytesIO.from io import StringIO
def count_lines(file_obj):
return sum(1 for _ in file_obj)
fake = StringIO("a\nb\nc\n")
print(count_lines(fake)) # 3
StringIO и BytesIO — это быстрый способ получить все преимущества файлового интерфейса без лишнего I/O. Виртуальный файл в несколько строк кода.🔥4❤1
Python для начинающих: работаем с конфигурационными файлами через
Хардкодить настройки в код — как хранить пароль от квартиры на стикере у двери. Удобно ровно до первого взлома. В Python для таких вещей есть модуль
---
### Простой пример: читаем настройки из
Пусть у нас есть файл
Читаем его в Python:
Заметь:
-
-
---
### Значения по умолчанию
Если ключа нет — можно задать дефолты, чтобы код не падал:
---
### Создаём и сохраняем конфиг из кода
Теперь у вас есть автогенерируемый файл настроек — удобно, если приложение запускается впервые и должно создать "стартовый" конфиг.
---
### Переменные и интерполяция
Фишка
---
### Когда
- Небольшие приложения и скрипты с простыми настройками.
- Конфиги, которые редактируют люди (админы, DevOps, вы сами через год).
- Сценарии, где важна читаемость и простота, а не сложные структуры.
Для вложенных структур удобнее
configparserХардкодить настройки в код — как хранить пароль от квартиры на стикере у двери. Удобно ровно до первого взлома. В Python для таких вещей есть модуль
configparser, который позволяет хранить настройки в отдельных .ini‑файлах: удобно, читаемо и без переписывания кода при каждом изменении.---
### Простой пример: читаем настройки из
.iniПусть у нас есть файл
settings.ini:[database]
host = localhost
port = 5432
user = admin
[app]
debug = true
log_level = INFO
Читаем его в Python:
import configparser
config = configparser.ConfigParser()
config.read("settings.ini")
db_host = config["database"]["host"]
db_port = config.getint("database", "port")
debug_mode = config.getboolean("app", "debug")
print(db_host, db_port, debug_mode)
Заметь:
-
config["section"]["key"] — обычное строковое значение; -
getint, getboolean, getfloat — сразу приводят к нужному типу.---
### Значения по умолчанию
Если ключа нет — можно задать дефолты, чтобы код не падал:
log_level = config.get("app", "log_level", fallback="WARNING")
fallback вернёт значение по умолчанию, если ключ отсутствует.---
### Создаём и сохраняем конфиг из кода
configparser позволяет не только читать, но и создавать .ini‑файлы:import configparser
config = configparser.ConfigParser()
config["database"] = {
"host": "localhost",
"port": "3306",
"user": "root"
}
config["app"] = {}
config["app"]["debug"] = "false"
config["app"]["log_level"] = "ERROR"
with open("generated_settings.ini", "w") as configfile:
config.write(configfile)
Теперь у вас есть автогенерируемый файл настроек — удобно, если приложение запускается впервые и должно создать "стартовый" конфиг.
---
### Переменные и интерполяция
Фишка
configparser — возможность ссылаться на переменные:[paths]
base_dir = /usr/local/app
logs_dir = %(base_dir)s/logs
import configparser
config = configparser.ConfigParser()
config.read("paths.ini")
logs_dir = config["paths"]["logs_dir"]
print(logs_dir) # /usr/local/app/logs
---
### Когда
configparser — хороший выбор- Небольшие приложения и скрипты с простыми настройками.
- Конфиги, которые редактируют люди (админы, DevOps, вы сами через год).
- Сценарии, где важна читаемость и простота, а не сложные структуры.
Для вложенных структур удобнее
JSON или YAML, но для классических "секций и ключей" configparser — лёгкий, встроенный и очень практичный инструмент.👍4
### Изучение типа
У множества в Python есть «старший брат-интроверт» — неизменяемый
---
#### Что такое
Создание:
Как и обычные множества,
---
#### Главное преимущество: хешируемость
Из-за неизменяемости
1. Использовать как ключ в словаре
2. Класть внутрь других множеств
То, чего нельзя сделать с обычным
Такая схема удобна, когда комбинация флагов или прав доступа сама по себе является ключом.
---
#### Неизменяемые конфигурации
Функция может быть уверена:
---
#### Использование в кэше и мемоизации
Иногда аргументом функции является коллекция, а результат хочется кэшировать в словаре. Список и обычный
Теперь независимо от порядка элементов в коллекции кэш будет работать корректно.
---
#### Операции, которые доступны
У
- поддерживаются:
- отсутствуют:
Каждая операция возвращает новый
---
#### Когда стоит выбрать именно
- Набор значений должен быть константой.
- Нужно использовать множество как ключ словаря или элемент другого множества.
- Важна защита от случайных изменений данных.
- Аргументы функции — коллекции, которые участвуют в кэшировании.
frozenset: когда он полезен в программированииУ множества в Python есть «старший брат-интроверт» — неизменяемый
frozenset. Снаружи он ведёт себя почти как обычный set, но есть одно ключевое отличие: его нельзя изменить после создания. Зачем это нужно и где это действительно помогает?---
#### Что такое
frozensetset — изменяемый: в него можно добавлять и удалять элементы. frozenset — immutable: после создания его содержимое фиксировано.Создание:
permissions = frozenset(["read", "write"])
empty = frozenset()
from_set = frozenset({1, 2, 3})
Как и обычные множества,
frozenset хранит только уникальные элементы и не гарантирует порядок.---
#### Главное преимущество: хешируемость
Из-за неизменяемости
frozenset можно:1. Использовать как ключ в словаре
2. Класть внутрь других множеств
То, чего нельзя сделать с обычным
set.user_permissions = {
frozenset(["read", "write"]): "editor",
frozenset(["read"]): "viewer",
}
role = user_permissions[frozenset(["read", "write"])]
print(role) # editor
Такая схема удобна, когда комбинация флагов или прав доступа сама по себе является ключом.
---
#### Неизменяемые конфигурации
frozenset отлично подходит для описания «застывших» наборов параметров, где важна гарантия, что данные никто не изменит случайно:SAFE_EXTENSIONS = frozenset([".jpg", ".png", ".gif"])
def is_safe_extension(ext: str) -> bool:
return ext.lower() in SAFE_EXTENSIONS
Функция может быть уверена:
SAFE_EXTENSIONS не изменят изнутри другой части кода.---
#### Использование в кэше и мемоизации
Иногда аргументом функции является коллекция, а результат хочется кэшировать в словаре. Список и обычный
set использовать нельзя — они не хешируемы. frozenset решает проблему:cache = {}
def expensive_calc(items):
key = frozenset(items)
if key in cache:
return cache[key]
result = sum(items) # вместо тяжёлых вычислений
cache[key] = result
return result
Теперь независимо от порядка элементов в коллекции кэш будет работать корректно.
---
#### Операции, которые доступны
У
frozenset есть все стандартные операции множеств, кроме тех, что изменяют объект:- поддерживаются:
union, intersection, difference, issubset, issuperset и операторы |, &, -, ^;- отсутствуют:
add, remove, discard, clear, update.a = frozenset([1, 2, 3])
b = frozenset([3, 4])
print(a & b) # frozenset({3})
print(a | b) # frozenset({1, 2, 3, 4})
Каждая операция возвращает новый
frozenset.---
#### Когда стоит выбрать именно
frozenset- Набор значений должен быть константой.
- Нужно использовать множество как ключ словаря или элемент другого множества.
- Важна защита от случайных изменений данных.
- Аргументы функции — коллекции, которые участвуют в кэшировании.
frozenset — это маленький, но очень полезный инструмент, особенно когда вы начинаете думать о надёжности и предсказуемости данных в крупных проектах.👍5
### Создание асинхронного парсера с
Синхронный парсер — это как официант, который берет один заказ и не подходит к другим столам, пока не принесет блюдо. Асинхронный парсер — тот же официант, но он умеет параллелить ожидание: заказал — пока готовят, пошел к следующему столу.
В Python такой “официант” — связка
---
### Базовая идея:
Асинхронность в Python строится вокруг корутин:
-
-
---
### Минимальный асинхронный запрос
Установим библиотеку:
Простой пример: скачать одну страницу.
Ключевые моменты:
-
-
-
---
### Переходим к настоящему парсеру: много URL сразу
Синхронно опрашивать 50 страниц — значит 50 раз ждать сети по очереди. Асинхронно — ждать их почти одновременно.
Что здесь важно:
-
-
- Ошибки ловим внутри
---
### Добавляем ограничение параллелизма
Если URL десятки тысяч, лупить по ним всем сразу — плохая идея. Ограничим число одновременных запросов:
---
### Куда двигаться дальше
- Разбирать HTML с
- Сохранять результаты в
- Добавлять ретраи, случайные задержки и прокси, если вы парсите капризные сайты.
Асинхронный парсер — это мощный инструмент: вы упираетесь уже не в Python, а в скорость сети и лимиты сайтов. Главное — внимательно относиться к таймаутам, лимитам запросов и правилам ресурса, который вы парсите.
aiohttp и asyncioСинхронный парсер — это как официант, который берет один заказ и не подходит к другим столам, пока не принесет блюдо. Асинхронный парсер — тот же официант, но он умеет параллелить ожидание: заказал — пока готовят, пошел к следующему столу.
В Python такой “официант” — связка
asyncio + aiohttp. С их помощью можно за секунды обходить десятки и сотни страниц, не создавая кучу потоков.---
### Базовая идея:
async / awaitАсинхронность в Python строится вокруг корутин:
-
async def — объявляет корутину;-
await — говорит: “подожди результат, а пока можно заняться другим”.---
### Минимальный асинхронный запрос
Установим библиотеку:
pip install aiohttp
Простой пример: скачать одну страницу.
import asyncio
import aiohttp
async def fetch_html(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
async with aiohttp.ClientSession() as session:
html = await fetch_html(session, "https://example.com")
print(len(html))
asyncio.run(main())
Ключевые моменты:
-
ClientSession переиспользует соединения — это быстрее и экономнее.-
async with гарантирует корректное закрытие соединений.-
await response.text() не блокирует весь поток, пока сервер отвечает.---
### Переходим к настоящему парсеру: много URL сразу
Синхронно опрашивать 50 страниц — значит 50 раз ждать сети по очереди. Асинхронно — ждать их почти одновременно.
import asyncio
import aiohttp
URLS = [
"https://example.com",
"https://httpbin.org/get",
"https://python.org",
]
async def fetch_status(session, url):
try:
async with session.get(url, timeout=5) as response:
return url, response.status
except Exception as e:
return url, f"error: {e}"
async def main():
async with aiohttp.ClientSession() as session:
tasks = [fetch_status(session, url) for url in URLS]
results = await asyncio.gather(*tasks)
for url, status in results:
print(url, "->", status)
asyncio.run(main())
Что здесь важно:
-
tasks = [...] создаёт список корутин, которые будут выполняться конкурентно.-
asyncio.gather запускает их и ждёт, пока все завершатся.- Ошибки ловим внутри
fetch_status, чтобы не завалить всё выполнение из‑за одного упавшего сайта.---
### Добавляем ограничение параллелизма
Если URL десятки тысяч, лупить по ним всем сразу — плохая идея. Ограничим число одновременных запросов:
import asyncio
import aiohttp
from asyncio import Semaphore
async def fetch_limited(session, url, sem):
async with sem:
async with session.get(url) as response:
return url, response.status
async def main():
sem = Semaphore(10) # не больше 10 запросов одновременно
urls = [f"https://httpbin.org/delay/1?i={i}" for i in range(50)]
async with aiohttp.ClientSession() as session:
tasks = [fetch_limited(session, url, sem) for url in urls]
for url, status in await asyncio.gather(*tasks):
print(url, "->", status)
asyncio.run(main())
---
### Куда двигаться дальше
- Разбирать HTML с
BeautifulSoup или lxml внутри fetch_*.- Сохранять результаты в
asyncpg или aiosqlite.- Добавлять ретраи, случайные задержки и прокси, если вы парсите капризные сайты.
Асинхронный парсер — это мощный инструмент: вы упираетесь уже не в Python, а в скорость сети и лимиты сайтов. Главное — внимательно относиться к таймаутам, лимитам запросов и правилам ресурса, который вы парсите.
🔥4👍2
Python для начинающих: основы работы с очередями задач и библиотекой RQ
Если ваш скрипт регулярно делает «тяжелые» вещи — обрабатывает изображения, шлет письма, дергает внешние API, — рано или поздно вы упретесь в задержки. Пользователь кликает кнопку, а страница «думает» 10 секунд. Некрасиво.
Выход — вынести тяжелую работу в фоновую очередь задач. Одна из самых простых библиотек для этого в Python — RQ (Redis Queue).
---
### Что такое RQ в двух словах
RQ — это:
- Redis как хранилище очередей;
- worker-процессы, которые забирают задачи из очереди;
- декоратор или функция enqueue, чтобы отправить задачу в фон.
Схема проста: ваш веб-код быстро ставит задачу в очередь и сразу отвечает пользователю, а worker спокойно делает работу «за кулисами».
---
### Установка и базовый пример
Устанавливаем:
Нужен запущенный Redis-сервер.
Пусть у нас есть файл
Теперь добавим задачу в очередь:
Запускаем worker:
Worker подключится к Redis, увидит задачу и выполнит
---
### Проверка состояния задачи
RQ позволяет отслеживать статус:
---
### Почему это удобно
- Не блокируете основной поток (веб-запрос, CLI-интерфейс).
- Легко масштабировать: просто запускаете больше worker-процессов.
- Простая интеграция с Flask/Django: вместо «сделать сейчас» —
Для начала работы с фоновыми задачами RQ — отличный инструмент: минимум магии, максимум понятности. Попробуйте вынести в очередь все, что занимает больше пары секунд, и вы увидите, насколько живее станет ваше приложение.
Если ваш скрипт регулярно делает «тяжелые» вещи — обрабатывает изображения, шлет письма, дергает внешние API, — рано или поздно вы упретесь в задержки. Пользователь кликает кнопку, а страница «думает» 10 секунд. Некрасиво.
Выход — вынести тяжелую работу в фоновую очередь задач. Одна из самых простых библиотек для этого в Python — RQ (Redis Queue).
---
### Что такое RQ в двух словах
RQ — это:
- Redis как хранилище очередей;
- worker-процессы, которые забирают задачи из очереди;
- декоратор или функция enqueue, чтобы отправить задачу в фон.
Схема проста: ваш веб-код быстро ставит задачу в очередь и сразу отвечает пользователю, а worker спокойно делает работу «за кулисами».
---
### Установка и базовый пример
Устанавливаем:
pip install rq redis
Нужен запущенный Redis-сервер.
Пусть у нас есть файл
tasks.py:# tasks.py
import time
def process_file(file_path: str) -> str:
print(f"Start processing {file_path}")
time.sleep(5) # heavy work imitation
return f"Processed: {file_path}"
Теперь добавим задачу в очередь:
# enqueue_task.py
from redis import Redis
from rq import Queue
from tasks import process_file
redis_conn = Redis()
q = Queue("default", connection=redis_conn)
job = q.enqueue(process_file, "/tmp/data.csv")
print("Job ID:", job.id)
Запускаем worker:
rq worker default
Worker подключится к Redis, увидит задачу и выполнит
process_file в фоне.---
### Проверка состояния задачи
RQ позволяет отслеживать статус:
from rq import Queue
from rq.job import Job
from redis import Redis
redis_conn = Redis()
q = Queue("default", connection=redis_conn)
job = Job.fetch("your-job-id", connection=redis_conn)
print(job.get_status()) # queued, started, finished, failed
print(job.result) # результат после выполнения
---
### Почему это удобно
- Не блокируете основной поток (веб-запрос, CLI-интерфейс).
- Легко масштабировать: просто запускаете больше worker-процессов.
- Простая интеграция с Flask/Django: вместо «сделать сейчас» —
q.enqueue(...).Для начала работы с фоновыми задачами RQ — отличный инструмент: минимум магии, максимум понятности. Попробуйте вынести в очередь все, что занимает больше пары секунд, и вы увидите, насколько живее станет ваше приложение.
🔥3👍1
Как использовать модули
Новички в Python часто удивляются: почему
Сегодня разберёмся, когда достаточно модуля
---
## Модуль
- тригонометрии;
- корней, логарифмов;
- работы с константами (
Погрешности здесь небольшие и в научных расчётах обычно приемлемые.
---
## Где
В финансовых и бухгалтерских задачах ошибка в 0.01 уже критична.
Человек видит 0.3, компьютер — нет. Для денег и точных десятичных значений нужен
---
## Модуль
Ключевой момент — создавать
---
## Управление точностью через контекст
Можно выбирать и стратегию округления (например, банковское округление), что критично для расчёта налогов, процентов, комиссий.
---
## Когда что использовать
-
Физика, геометрия, машинное обучение, игры — где микропогрешности не страшны, а скорость важнее.
-
Деньги, пересчёт валют, бухгалтерия, точные отчёты — там, где каждая копейка на счету.
Если у вас в задаче фигурируют «рубли», «копейки», «проценты по кредиту» — почти наверняка нужен
math и decimal для точных вычисленийНовички в Python часто удивляются: почему
0.1 + 0.2 != 0.3? Компьютер ведь «строго математичный»… но нет. Виновата двоичная плавающая точка: не все десятичные дроби могут быть точно представлены в памяти.Сегодня разберёмся, когда достаточно модуля
math, а когда нужно доставать тяжёлую артиллерию — decimal.---
## Модуль
math: быстро и по-научномуmath работает с типом float. Он быстрый, удобный и идеально подходит для:- тригонометрии;
- корней, логарифмов;
- работы с константами (
pi, e).import math
radius = 2.5
circle_area = math.pi * radius ** 2
print(circle_area) # 19.634954084936208
x = 0.5
print(math.sin(x), math.log(x), math.sqrt(x))
Погрешности здесь небольшие и в научных расчётах обычно приемлемые.
---
## Где
float подводитВ финансовых и бухгалтерских задачах ошибка в 0.01 уже критична.
price = 0.1
total = price * 3
print(total) # 0.30000000000000004
print(total == 0.3) # False
Человек видит 0.3, компьютер — нет. Для денег и точных десятичных значений нужен
decimal.---
## Модуль
decimal: точные деньги и не толькоdecimal.Decimal хранит число как десятичное, а не двоичное представление.from decimal import Decimal
price = Decimal("0.1")
total = price * 3
print(total) # 0.3
print(total == Decimal("0.3")) # True
Ключевой момент — создавать
Decimal из строки, а не из float, иначе потащите с собой уже округлённую двоичную ошибку.---
## Управление точностью через контекст
decimal позволяет настраивать точность и режимы округления.from decimal import Decimal, getcontext
getcontext().prec = 6 # количество значащих цифр
x = Decimal("1") / Decimal("7")
print(x) # 0.142857
getcontext().prec = 28
y = Decimal("1") / Decimal("7")
print(y) # 0.1428571428571428571428571429
Можно выбирать и стратегию округления (например, банковское округление), что критично для расчёта налогов, процентов, комиссий.
---
## Когда что использовать
-
math + float Физика, геометрия, машинное обучение, игры — где микропогрешности не страшны, а скорость важнее.
-
decimal Деньги, пересчёт валют, бухгалтерия, точные отчёты — там, где каждая копейка на счету.
Если у вас в задаче фигурируют «рубли», «копейки», «проценты по кредиту» — почти наверняка нужен
decimal. Если «угол», «скорость», «синус» — смело берите math.❤4👍1