Работа с фоновыми задачами в 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
Чтение и модификация PDF с помощью PyPDF2
PDF‑файлы часто кажутся чем-то «закрытым»: удобно читать, сложно менять. Но в Python есть библиотека, которая ломает этот миф, — PyPDF2. С ее помощью можно собирать отчеты из нескольких файлов, вытаскивать текст, разбирать документы по страницам и даже ставить водяные знаки.
Установим библиотеку:
---
### Чтение PDF и извлечение текста
Начнем с базового: как прочитать PDF и вытащить из него текст.
Важно: извлечение текста зависит от структуры PDF. В нормально сформированных документах работает отлично, а вот сканы без распознавания текста будут пустыми — там нужен уже OCR (например, Tesseract), а не PyPDF2.
---
### Объединение нескольких PDF
Представьте, что нужно склеить несколько отчетов в один общий файл:
Так можно быстро собирать итоговые документы из шаблонов, глав, отдельных приложений и т.д.
---
### Разделение PDF по страницам
Теперь наоборот: нужно разрезать большой PDF на отдельные файлы, по странице в каждом.
Так удобно разбивать сканы документов, договоры, длинные инструкции.
---
### Водяной знак на каждую страницу
Частая задача — пометить документ водяным знаком «Confidential»:
Файл
---
PyPDF2 — отличный инструмент, чтобы перестать воспринимать PDF как «нередактируемый камень». С его помощью легко автоматизировать отчеты, подготовку документов, рассылки и архивирование — прямо из вашего кода на Python.
PDF‑файлы часто кажутся чем-то «закрытым»: удобно читать, сложно менять. Но в Python есть библиотека, которая ломает этот миф, — PyPDF2. С ее помощью можно собирать отчеты из нескольких файлов, вытаскивать текст, разбирать документы по страницам и даже ставить водяные знаки.
Установим библиотеку:
pip install pypdf2
---
### Чтение PDF и извлечение текста
Начнем с базового: как прочитать PDF и вытащить из него текст.
from PyPDF2 import PdfReader
reader = PdfReader("report.pdf")
print("Pages:", len(reader.pages))
page_0 = reader.pages[0]
text = page_0.extract_text()
print(text)
Важно: извлечение текста зависит от структуры PDF. В нормально сформированных документах работает отлично, а вот сканы без распознавания текста будут пустыми — там нужен уже OCR (например, Tesseract), а не PyPDF2.
---
### Объединение нескольких PDF
Представьте, что нужно склеить несколько отчетов в один общий файл:
from PyPDF2 import PdfMerger
merger = PdfMerger()
files = ["intro.pdf", "chapter1.pdf", "chapter2.pdf"]
for file_name in files:
merger.append(file_name)
merger.write("full_report.pdf")
merger.close()
Так можно быстро собирать итоговые документы из шаблонов, глав, отдельных приложений и т.д.
---
### Разделение PDF по страницам
Теперь наоборот: нужно разрезать большой PDF на отдельные файлы, по странице в каждом.
from PyPDF2 import PdfReader, PdfWriter
reader = PdfReader("big_document.pdf")
for i, page in enumerate(reader.pages):
writer = PdfWriter()
writer.add_page(page)
output_name = f"page_{i + 1}.pdf"
with open(output_name, "wb") as output_file:
writer.write(output_file)
Так удобно разбивать сканы документов, договоры, длинные инструкции.
---
### Водяной знак на каждую страницу
Частая задача — пометить документ водяным знаком «Confidential»:
from PyPDF2 import PdfReader, PdfWriter
base_reader = PdfReader("original.pdf")
watermark_reader = PdfReader("watermark.pdf")
watermark_page = watermark_reader.pages[0]
writer = PdfWriter()
for page in base_reader.pages:
page.merge_page(watermark_page)
writer.add_page(page)
with open("watermarked.pdf", "wb") as output_file:
writer.write(output_file)
Файл
watermark.pdf — это обычная страница с текстом/логотипом, заранее подготовленная в любом редакторе.---
PyPDF2 — отличный инструмент, чтобы перестать воспринимать PDF как «нередактируемый камень». С его помощью легко автоматизировать отчеты, подготовку документов, рассылки и архивирование — прямо из вашего кода на Python.
👍5
Использование библиотеки
Когда выводишь данные в консоль обычными
---
### Установка
И можно начинать.
---
### Базовый пример: список списков
Результат будет вроде:
Без ручного выравнивания — всё красиво само.
---
### Разные форматы таблиц
Можно подобрать стиль под задачу: для документации —
---
### Работа со словарями
Необязательно собирать данные в списки — можно использовать списки словарей.
---
### Выравнивание и формат чисел
Числа часто хочется выровнять по правому краю и красиво отформатировать.
---
tabulate для красивого вывода таблицКогда выводишь данные в консоль обычными
print, быстро наступает хаос: всё съезжает, столбцы пляшут, читать неудобно. Библиотека tabulate решает это за нас — она превращает списки и словари в аккуратные таблицы в одну строку кода.---
### Установка
pip install tabulate
И можно начинать.
---
### Базовый пример: список списков
from tabulate import tabulate
data = [
["Alice", 23, "Engineer"],
["Bob", 31, "Designer"],
["Charlie", 27, "Developer"],
]
headers = ["Name", "Age", "Job"]
print(tabulate(data, headers=headers))
Результат будет вроде:
Name Age Job
------- ---- ----------
Alice 23 Engineer
Bob 31 Designer
Charlie 27 Developer
Без ручного выравнивания — всё красиво само.
---
### Разные форматы таблиц
tabulate умеет множество стилей: "grid", "fancy_grid", "github", "pipe", "pretty" и др.from tabulate import tabulate
data = [
["Book", 12.5],
["Pen", 1.2],
["Notebook", 3.99],
]
headers = ["Item", "Price"]
print(tabulate(data, headers=headers, tablefmt="grid"))
print()
print(tabulate(data, headers=headers, tablefmt="github"))
Можно подобрать стиль под задачу: для документации —
"github", для наглядности в консоли — "grid" или "fancy_grid".---
### Работа со словарями
Необязательно собирать данные в списки — можно использовать списки словарей.
from tabulate import tabulate
users = [
{"name": "Alice", "score": 95},
{"name": "Bob", "score": 87},
{"name": "Charlie", "score": 92},
]
print(tabulate(users, headers="keys", tablefmt="fancy_grid"))
headers="keys" говорит: взять ключи словарей как названия столбцов.---
### Выравнивание и формат чисел
Числа часто хочется выровнять по правому краю и красиво отформатировать.
from tabulate import tabulate
data = [
["USD", 1.0],
["EUR", 0.93],
["JPY", 147.25],
]
headers = ["Currency", "Rate"]
print(tabulate(
data,
headers=headers,
tablefmt="github",
floatfmt=".2f", # два знака после запятой
colalign=("left", "right"), # выравнивание столбцов
))
---
tabulate — идеальный помощник, когда нужно быстро привести данные в понятный вид: логи, результаты скриптов, небольшие отчёты. Одна строка — и у вас уже «мини-Excel» прямо в терминале.👍5
Создаем простой консольный файловый менеджер на Python
Файловый менеджер — отличный мини-проект для прокачки работы с файловой системой, модуля
Идея: написать скрипт, который умеет:
- показывать текущую папку и её содержимое
- переходить по каталогам
- создавать и удалять файлы/папки
- копировать и переименовывать файлы
Основные инструменты:
-
-
-
---
### Структура команд
Сделаем формат:
Теперь реализуем ядро менеджера.
---
Что можно улучшить дальше:
- добавить цветной вывод (
- историю команд
- безопасное удаление (перемещение в «корзину»)
- конфиг с «домашней» директорией
Такой мини-проект одновременно тренирует работу с модулями, аргументами, обработкой ошибок и структурой кода — идеальный шаг от учебных задачек к реальным утилитам.
Файловый менеджер — отличный мини-проект для прокачки работы с файловой системой, модуля
os и аргументами командной строки. Без GUI, только консоль и код.Идея: написать скрипт, который умеет:
- показывать текущую папку и её содержимое
- переходить по каталогам
- создавать и удалять файлы/папки
- копировать и переименовывать файлы
Основные инструменты:
-
os — работа с путями и директориями -
shutil — копирование и перемещение -
sys.argv — обработка команд, переданных при запуске---
### Структура команд
Сделаем формат:
python fm.py ls
python fm.py cd new_folder
python fm.py touch notes.txt
python fm.py rm notes.txt
python fm.py mkdir projects
python fm.py cp src.txt backup.txt
python fm.py mv old.txt new.txt
Теперь реализуем ядро менеджера.
import os
import sys
import shutil
def list_dir():
print("Current dir:", os.getcwd())
for name in os.listdir():
mark = "/" if os.path.isdir(name) else ""
print(f"- {name}{mark}")
def change_dir(path):
try:
os.chdir(path)
print("Changed dir to:", os.getcwd())
except FileNotFoundError:
print("Path not found")
def make_file(name):
if os.path.exists(name):
print("File already exists")
return
with open(name, "w", encoding="utf-8") as f:
pass
print("File created:", name)
def remove_path(path):
if os.path.isdir(path):
shutil.rmtree(path)
print("Directory removed:", path)
elif os.path.isfile(path):
os.remove(path)
print("File removed:", path)
else:
print("Nothing to remove")
def make_dir(name):
os.makedirs(name, exist_ok=True)
print("Directory created:", name)
def copy_file(src, dst):
if not os.path.isfile(src):
print("Source file not found")
return
shutil.copy2(src, dst)
print(f"Copied {src} -> {dst}")
def move_file(src, dst):
if not os.path.exists(src):
print("Source not found")
return
shutil.move(src, dst)
print(f"Moved {src} -> {dst}")
def main():
if len(sys.argv) < 2:
print("Usage: fm.py [ls|cd|touch|rm|mkdir|cp|mv] ...")
return
cmd = sys.argv[1]
args = sys.argv[2:]
if cmd == "ls":
list_dir()
elif cmd == "cd" and args:
change_dir(args[0])
elif cmd == "touch" and args:
make_file(args[0])
elif cmd == "rm" and args:
remove_path(args[0])
elif cmd == "mkdir" and args:
make_dir(args[0])
elif cmd == "cp" and len(args) == 2:
copy_file(args[0], args[1])
elif cmd == "mv" and len(args) == 2:
move_file(args[0], args[1])
else:
print("Unknown or invalid command")
if __name__ == "__main__":
main()
---
Что можно улучшить дальше:
- добавить цветной вывод (
colorama) - историю команд
- безопасное удаление (перемещение в «корзину»)
- конфиг с «домашней» директорией
Такой мини-проект одновременно тренирует работу с модулями, аргументами, обработкой ошибок и структурой кода — идеальный шаг от учебных задачек к реальным утилитам.
👍5🔥1
Построение простой системы рекомендаций на основе покупок
Представим, что у нас есть небольшой онлайн‑магазин, и мы хотим показывать покупателю блок «С этим товаром часто покупают». Звучит как магия, но на базовом уровне это можно сделать несколькими строками кода на Python.
Базовая идея:
если товары A и B часто встречаются в одной корзине, то покупателям товара A можно рекомендовать B (и наоборот).
### Данные
Допустим, у нас есть список заказов, и каждый заказ — это набор купленных товаров:
### Считаем совместные покупки
Используем
Так мы получаем «рейтинг дружбы» товаров: чем больше счётчик, тем чаще товары покупают вместе.
### Строим простые рекомендации
Сделаем функцию, которая по товару отдаёт список рекомендаций:
Теперь для любого товара мы можем быстро получить список «часто покупаемых вместе».
### Что можно улучшить
1. Учесть частоту самого товара — нормировать по общему числу покупок товара (меры вроде lift, confidence).
2. Фильтровать редкие пары с помощью
3. Сохранить модель (словарь
Это ещё не «умный» AI, но уже рабочая рекомендательная система, которую легко встроить в учебный проект интернет‑магазина и постепенно усложнять: добавлять веса, категории, сезонность и, конечно, больше данных.
Представим, что у нас есть небольшой онлайн‑магазин, и мы хотим показывать покупателю блок «С этим товаром часто покупают». Звучит как магия, но на базовом уровне это можно сделать несколькими строками кода на Python.
Базовая идея:
если товары A и B часто встречаются в одной корзине, то покупателям товара A можно рекомендовать B (и наоборот).
### Данные
Допустим, у нас есть список заказов, и каждый заказ — это набор купленных товаров:
transactions = [
["milk", "bread", "eggs"],
["bread", "butter"],
["milk", "bread"],
["beer", "chips"],
["beer", "chips", "nuts"],
["milk", "eggs"],
]
### Считаем совместные покупки
Используем
collections.Counter, чтобы посчитать, какие пары товаров встречаются вместе чаще всего:from collections import Counter
from itertools import combinations
pair_counter = Counter()
for basket in transactions:
# все уникальные пары товаров из одной корзины
for item1, item2 in combinations(sorted(set(basket)), 2):
pair_counter[(item1, item2)] += 1
print(pair_counter.most_common(5))
Так мы получаем «рейтинг дружбы» товаров: чем больше счётчик, тем чаще товары покупают вместе.
### Строим простые рекомендации
Сделаем функцию, которая по товару отдаёт список рекомендаций:
from collections import defaultdict
def build_recommendations(pair_counter, min_support=1):
related = defaultdict(list)
for (item1, item2), count in pair_counter.items():
if count < min_support:
continue
related[item1].append((item2, count))
related[item2].append((item1, count))
# сортируем по популярности совместной покупки
for item in related:
related[item].sort(key=lambda x: x[1], reverse=True)
return related
recommendations = build_recommendations(pair_counter, min_support=1)
def recommend_for(item, top_n=3):
return [x for x, _ in recommendations.get(item, [])[:top_n]]
print("For 'milk':", recommend_for("milk"))
print("For 'beer':", recommend_for("beer"))
Теперь для любого товара мы можем быстро получить список «часто покупаемых вместе».
### Что можно улучшить
1. Учесть частоту самого товара — нормировать по общему числу покупок товара (меры вроде lift, confidence).
2. Фильтровать редкие пары с помощью
min_support, чтобы не советовать то, что встретилось один раз случайно.3. Сохранить модель (словарь
recommendations) в файл через json или pickle и подгружать в вашем веб‑приложении.Это ещё не «умный» AI, но уже рабочая рекомендательная система, которую легко встроить в учебный проект интернет‑магазина и постепенно усложнять: добавлять веса, категории, сезонность и, конечно, больше данных.
👍4❤1