Python для начинающих
1.24K subscribers
546 photos
3 videos
232 files
74 links
Python для начинающих
Download Telegram
Как использовать 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
Работа с фоновыми задачами с помощью модуля threading
Работа с фоновыми задачами в 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
Изучение io.StringIO и BytesIO: виртуальные файлы в Python

Иногда файл нужен… но создавать его на диске совсем не хочется. Лишние записи на диск, временные файлы, уборка за собой — все это раздражает. Именно тут в игру вступают 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. Виртуальный файл в несколько строк кода.
🔥41
Работа с конфигурационными файлами с использованием configparser
Python для начинающих: работаем с конфигурационными файлами через 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
Изучение типа frozenset: когда он полезен в программировании
### Изучение типа frozenset: когда он полезен в программировании

У множества в Python есть «старший брат-интроверт» — неизменяемый frozenset. Снаружи он ведёт себя почти как обычный set, но есть одно ключевое отличие: его нельзя изменить после создания. Зачем это нужно и где это действительно помогает?

---

#### Что такое frozenset

set — изменяемый: в него можно добавлять и удалять элементы.
frozensetimmutable: после создания его содержимое фиксировано.

Создание:

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
Создание асинхронного парсера с aiohttp и asyncio
1
### Создание асинхронного парсера с 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
Основы работы с очередями задач: библиотека RQ
Python для начинающих: основы работы с очередями задач и библиотекой 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
Как использовать модули math и decimal для точных вычислений
Как использовать модули 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 с помощью PyPDF2

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 для красивого вывода таблиц
Использование библиотеки 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
Создание простого файлового менеджера на консоли