Генерация диаграмм с pygraphviz: основы визуализации графов
Иногда проще один раз увидеть, чем сто раз распечатать
---
### Установка
Важно: на некоторых системах сначала нужно установить сам Graphviz (через пакетный менеджер ОС).
---
### Простейший граф
Создадим ориентированный граф и сохраним его как PNG:
---
### Стайлинг: делаем диаграмму понятной
Граф без стиля — это просто клубок линий. Добавим цвета и формы, чтобы по диаграмме можно было ориентироваться за секунды:
Ключевые идеи:
-
-
-
---
### Быстрая визуализация структур из кода
---
Иногда проще один раз увидеть, чем сто раз распечатать
print(). Особенно когда дело касается связей: кто с кем соединен, в каком направлении идет поток данных, как устроена архитектура проекта. Для этого отлично подходит pygraphviz — оболочка над знаменитым Graphviz, позволяющая генерировать диаграммы прямо из Python.---
### Установка
pip install pygraphviz
Важно: на некоторых системах сначала нужно установить сам Graphviz (через пакетный менеджер ОС).
---
### Простейший граф
Создадим ориентированный граф и сохраним его как PNG:
from pygraphviz import AGraph
g = AGraph(directed=True)
g.add_node("User")
g.add_node("API")
g.add_node("DB")
g.add_edge("User", "API")
g.add_edge("API", "DB")
g.layout(prog="dot") # алгоритм раскладки
g.draw("simple_graph.png")
prog="dot" — классический и самый читаемый для иерархических структур (запросы сверху, база снизу).---
### Стайлинг: делаем диаграмму понятной
Граф без стиля — это просто клубок линий. Добавим цвета и формы, чтобы по диаграмме можно было ориентироваться за секунды:
from pygraphviz import AGraph
g = AGraph(directed=True, strict=True, rankdir="LR") # слева направо
g.add_node("Client", shape="box", style="filled", fillcolor="#AED6F1")
g.add_node("Service", shape="ellipse", style="filled", fillcolor="#A9DFBF")
g.add_node("Cache", shape="diamond", style="filled", fillcolor="#F9E79F")
g.add_edge("Client", "Service", label="HTTP")
g.add_edge("Service", "Cache", label="GET")
g.add_edge("Cache", "Service", label="HIT", color="green")
g.add_edge("Service", "Client", label="Response", color="blue")
g.graph_attr.update(label="Request Flow", fontsize="20")
g.layout(prog="dot")
g.draw("styled_graph.png")
Ключевые идеи:
-
rankdir="LR" — направление слева направо (удобно для потоков).-
shape, fillcolor, style — визуальное кодирование типов узлов.-
label и color у ребер помогают понимать протоколы, типы взаимодействия и т.п.---
### Быстрая визуализация структур из кода
pygraphviz удобно использовать для генерации диаграмм по данным из программы: граф зависимостей модулей, цепочка этапов обработки данных, схема микросервисов. Достаточно обхода вашей структуры (словаря, списка связей, дерева) и вызова add_node / add_edge в цикле.---
pygraphviz хорош тем, что избавляет от ручного рисования схем в редакторах: диаграмма становится частью кода, обновляется автоматически и всегда соответствует реальности. Для начинающего питониста это отличный инструмент, чтобы увидеть свои структуры данных и архитектуру, а не только представлять их в голове.👍1
Используем
Работа с файлами в Python часто начинается с модуля
Подключается просто:
## Копирование файлов
Базовый вариант —
Если нужны максимально точные «клоны» файла, есть
Разница — в сохранении метаданных (atime, mtime и т.п.). Для бэкапов это часто критично.
## Копирование папок
Для директорий используется
Важно: если
Так можно «обновлять» уже существующую папку бэкапа.
## Удаление файлов и папок
Одиночные файлы удобнее удалять через
Команда безвозвратная: корзины не будет, поэтому перед вызовом стоит дважды проверить путь или добавить «защиту от дурака»:
## Мини-утилита «скопировать и почистить»
Соберём всё вместе: скопируем папку и удалим старую:
Фактически мы реализовали аналог «перемещения» папки силами
---
shutil: копируем и удаляем файлы без болиРабота с файлами в Python часто начинается с модуля
os, но как только дело доходит до копирования и целых папок — на сцену выходит shutil. Это такой «швейцарский нож» для файловой системы.Подключается просто:
import shutil
from pathlib import Path
## Копирование файлов
Базовый вариант —
shutil.copy():src = Path("data/source.txt")
dst = Path("backup/source_copy.txt")
shutil.copy(src, dst)
copy переносит содержимое и права доступа, но не метаданные (например, время изменения).Если нужны максимально точные «клоны» файла, есть
copy2:shutil.copy2(src, dst)
Разница — в сохранении метаданных (atime, mtime и т.п.). Для бэкапов это часто критично.
## Копирование папок
Для директорий используется
copytree:src_dir = Path("data")
dst_dir = Path("data_backup")
shutil.copytree(src_dir, dst_dir)
Важно: если
data_backup уже существует, будет ошибка. В Python 3.8+ можно указать dirs_exist_ok=True:shutil.copytree(src_dir, dst_dir, dirs_exist_ok=True)
Так можно «обновлять» уже существующую папку бэкапа.
## Удаление файлов и папок
Одиночные файлы удобнее удалять через
Path.unlink(), а вот для целых деревьев директорий есть rmtree:target_dir = Path("old_logs")
shutil.rmtree(target_dir)
Команда безвозвратная: корзины не будет, поэтому перед вызовом стоит дважды проверить путь или добавить «защиту от дурака»:
if "backup" in str(target_dir):
shutil.rmtree(target_dir)
## Мини-утилита «скопировать и почистить»
Соберём всё вместе: скопируем папку и удалим старую:
def move_folder(src: Path, dst: Path):
shutil.copytree(src, dst, dirs_exist_ok=True)
shutil.rmtree(src)
move_folder(Path("tmp_uploads"), Path("storage/uploads"))
Фактически мы реализовали аналог «перемещения» папки силами
shutil.---
shutil хорош тем, что закрывает 80% задач по управлению файлами: копирование, рекурсивные операции, бэкапы, чистка временных директорий. А главное — код остаётся коротким и читаемым, без ручного обхода каталогов и велосипедов на os.listdir().🔥2👍1
Создание фейковых данных для тестирования с библиотекой Faker
Тестовые данные — боль любого начинающего разработчика. Ручное заполнение таблиц “Иван Иванов, test@test.ru, 123456” быстро превращает жизнь в скуку. К счастью, есть библиотека Faker, которая генерирует реалистичные фейковые данные: имена, адреса, телефоны, даты, тексты и даже фейковые компании.
---
## Установка
Базовое использование:
Каждый вызов вернет новое случайное, но правдоподобное значение.
---
## Локализация данных
Если вы тестируете российское приложение, логичнее видеть “Иван Петров”, а не “John Smith”:
Faker поддерживает десятки локалей:
---
## Генерация набора тестовых пользователей
Допустим, нужно быстро заполнить базу 10 фейковыми пользователями:
Метод
---
## Фейковые данные для API и фронтенда
Нужно протестировать список товаров или карточек в интерфейсе? Не проблема:
---
## Фиксация “сидов” для повторяемости
Важно, чтобы при тестах данные могли воспроизводиться. Для этого задаем seed:
---
Faker экономит часы рутины, делает тестовые данные живыми и разнообразными, а ваш код — более надежным. Попробуйте заменить все “test123” в ваших проектах на фейковые, но правдоподобные данные и посмотрите, сколько багов всплывет.
Тестовые данные — боль любого начинающего разработчика. Ручное заполнение таблиц “Иван Иванов, test@test.ru, 123456” быстро превращает жизнь в скуку. К счастью, есть библиотека Faker, которая генерирует реалистичные фейковые данные: имена, адреса, телефоны, даты, тексты и даже фейковые компании.
---
## Установка
pip install faker
Базовое использование:
from faker import Faker
fake = Faker()
print(fake.name())
print(fake.email())
print(fake.address())
Каждый вызов вернет новое случайное, но правдоподобное значение.
---
## Локализация данных
Если вы тестируете российское приложение, логичнее видеть “Иван Петров”, а не “John Smith”:
from faker import Faker
fake = Faker("ru_RU")
for _ in range(3):
print(fake.name(), "-", fake.phone_number())
Faker поддерживает десятки локалей:
en_US, de_DE, fr_FR и т.д.---
## Генерация набора тестовых пользователей
Допустим, нужно быстро заполнить базу 10 фейковыми пользователями:
from faker import Faker
fake = Faker("ru_RU")
def generate_user():
return {
"name": fake.name(),
"email": fake.unique.email(),
"address": fake.address(),
"birthdate": fake.date_of_birth(minimum_age=18, maximum_age=65),
}
users = [generate_user() for _ in range(10)]
for user in users:
print(user)
Метод
fake.unique гарантирует уникальность значения (пока не исчерпан генератор).---
## Фейковые данные для API и фронтенда
Нужно протестировать список товаров или карточек в интерфейсе? Не проблема:
from faker import Faker
import random
fake = Faker()
def generate_product():
return {
"id": fake.uuid4(),
"title": fake.sentence(nb_words=3),
"price": round(random.uniform(10, 500), 2),
"description": fake.text(max_nb_chars=80),
"in_stock": fake.boolean(chance_of_getting_true=80),
}
products = [generate_product() for _ in range(5)]
for product in products:
print(product)
---
## Фиксация “сидов” для повторяемости
Важно, чтобы при тестах данные могли воспроизводиться. Для этого задаем seed:
from faker import Faker
fake = Faker()
Faker.seed(42)
print(fake.name())
print(fake.name()) # при повторном запуске будут те же значения
---
Faker экономит часы рутины, делает тестовые данные живыми и разнообразными, а ваш код — более надежным. Попробуйте заменить все “test123” в ваших проектах на фейковые, но правдоподобные данные и посмотрите, сколько багов всплывет.
👍4
Изучаем таймеры и замеры времени с модулем
Когда код работает медленно, интуиция часто подводит. Кажется, что «вот это место точно тормозит», а на деле время утекает в другом участке. Здесь на сцену выходит модуль
### Почему не
Наивный подход:
Проблемы:
- одно измерение: могли попасть на фоновую нагрузку системы;
- точность зависит от платформы;
- неудобно сравнивать несколько вариантов.
### Базовый пример использования
Минимальный пример из консоли:
Параметры важны:
- первый аргумент — строка с кодом, который измеряем;
-
Но писать код в строках не всегда удобно. Лучше использовать функции и параметр
Так проще рефакторить и отлаживать.
### Сравниваем два варианта кода
Предположим, мы хотим понять, что быстрее: список или генератор в
На малых диапазонах разница может быть микроскопической, но тенденцию видно: списки часто чуть медленнее, потому что требуют дополнительной памяти.
### Использование
Если нужна подготовка данных, её не стоит включать в измеряемый код. Для этого есть
Здесь:
- в
- в
### Немного удобства:
Чтобы понять разброс по времени, используйте
Так можно ориентироваться на минимум как на «лучшие условия» выполнения.
---
timeitКогда код работает медленно, интуиция часто подводит. Кажется, что «вот это место точно тормозит», а на деле время утекает в другом участке. Здесь на сцену выходит модуль
timeit — простой способ точно измерить, что действительно медленнее, а что быстрее.### Почему не
time.time()?Наивный подход:
import time
start = time.time()
result = sum(range(10_000_000))
end = time.time()
print(end - start)
Проблемы:
- одно измерение: могли попасть на фоновую нагрузку системы;
- точность зависит от платформы;
- неудобно сравнивать несколько вариантов.
timeit решает это: он запускает код много раз, считает среднее время и делает это максимально «чисто».### Базовый пример использования
Минимальный пример из консоли:
import timeit
t = timeit.timeit("sum(range(1000))", number=10000)
print(t)
Параметры важны:
- первый аргумент — строка с кодом, который измеряем;
-
number — сколько раз выполнить этот код.Но писать код в строках не всегда удобно. Лучше использовать функции и параметр
stmt как объект:import timeit
def calc():
return sum(range(1000))
t = timeit.timeit(stmt=calc, number=10000)
print(t)
Так проще рефакторить и отлаживать.
### Сравниваем два варианта кода
Предположим, мы хотим понять, что быстрее: список или генератор в
sum.import timeit
def use_list():
return sum([i for i in range(1000)])
def use_gen():
return sum(i for i in range(1000))
t_list = timeit.timeit(use_list, number=10000)
t_gen = timeit.timeit(use_gen, number=10000)
print("list:", t_list)
print("gen :", t_gen)
На малых диапазонах разница может быть микроскопической, но тенденцию видно: списки часто чуть медленнее, потому что требуют дополнительной памяти.
### Использование
setupЕсли нужна подготовка данных, её не стоит включать в измеряемый код. Для этого есть
setup:import timeit
setup_code = "data = list(range(10000))"
stmt_code = """
result = 0
for x in data:
result += x
"""
t = timeit.timeit(stmt=stmt_code, setup=setup_code, number=1000)
print(t)
Здесь:
- в
setup мы генерируем данные один раз для каждого прогона timeit;- в
stmt считаем время только самого алгоритма.### Немного удобства:
repeatЧтобы понять разброс по времени, используйте
repeat:import timeit
def func():
return sum(range(10_000))
times = timeit.repeat(func, number=1000, repeat=5)
print(times) # список из 5 измерений
print("best:", min(times))
Так можно ориентироваться на минимум как на «лучшие условия» выполнения.
---
timeit — это карманный профилировщик для микробенчмарков. Он не заменяет полноценный профилировщик, но идеально подходит, когда нужно честно ответить на вопрос: «Этот способ действительно быстрее, или мне только кажется?»🔥4
Как использовать модуль
Когда файлов становится много, а отправить нужно «всё и сразу», на сцену выходит модуль
---
### Создаем ZIP‑архив из одного файла
Минимальный пример:
Что здесь происходит:
-
-
-
---
### Добавляем несколько файлов и папку
Чтобы пройтись по директории и заархивировать всё, что внутри:
Ключевой момент —
---
### Извлекаем архив целиком или выборочно
Извлечь всё содержимое очень просто:
А вот выборочное извлечение:
---
### Проверяем содержимое без распаковки
Иногда нужно просто «подглядеть» внутрь ZIP:
Так можно быстро оценить структуру и размер архива.
---
zipfile для создания и извлечения архивовКогда файлов становится много, а отправить нужно «всё и сразу», на сцену выходит модуль
zipfile. Он входит в стандартную библиотеку Python, так что ничего устанавливать не нужно.---
### Создаем ZIP‑архив из одного файла
Минимальный пример:
import zipfile
with zipfile.ZipFile("data.zip", mode="w", compression=zipfile.ZIP_DEFLATED) as zf:
zf.write("report.txt", arcname="report_in_zip.txt")
Что здесь происходит:
-
mode="w" — создаем новый архив (старый будет перезаписан).-
compression=ZIP_DEFLATED — обычное сжатие ZIP.-
write("report.txt", arcname=...) — первое имя — исходный файл, второе — как он будет называться внутри архива.---
### Добавляем несколько файлов и папку
Чтобы пройтись по директории и заархивировать всё, что внутри:
import zipfile
import os
def zip_folder(folder_path, zip_name):
with zipfile.ZipFile(zip_name, "w", zipfile.ZIP_DEFLATED) as zf:
for root, dirs, files in os.walk(folder_path):
for file_name in files:
full_path = os.path.join(root, file_name)
rel_path = os.path.relpath(full_path, folder_path)
zf.write(full_path, arcname=rel_path)
zip_folder("project", "project_backup.zip")
Ключевой момент —
rel_path. Благодаря относительному пути структура папок в архиве сохраняется аккуратно, без длинных системных путей.---
### Извлекаем архив целиком или выборочно
Извлечь всё содержимое очень просто:
import zipfile
with zipfile.ZipFile("project_backup.zip", "r") as zf:
zf.extractall("restored_project")
А вот выборочное извлечение:
import zipfile
with zipfile.ZipFile("project_backup.zip", "r") as zf:
for name in zf.namelist():
if name.endswith(".py"):
zf.extract(name, "only_scripts")
namelist() возвращает список файлов внутри архива. Можно фильтровать по расширению, имени и т.д.---
### Проверяем содержимое без распаковки
Иногда нужно просто «подглядеть» внутрь ZIP:
import zipfile
with zipfile.ZipFile("project_backup.zip", "r") as zf:
for info in zf.infolist():
print(info.filename, info.file_size, "bytes")
Так можно быстро оценить структуру и размер архива.
---
zipfile полезен для бэкапов, упаковки логов, раздачи учебных проектов и автоматизации рутины. Освоив эти несколько приёмов, вы легко встроите архивирование прямо в свои скрипты.👍2
Python для начинающих: приручаем командную строку с argparse
Рано или поздно любой скрипт вырастает из стадии «запустить и забыть» и ему хочется передавать параметры: путь к файлу, режим работы, уровень детализации логов. Жестко прописывать это в коде неудобно – каждый раз править и перезапускать. Тут и появляется герой дня — модуль
### Базовый пример: скрипт-калькулятор
Сделаем простой калькулятор, который принимает числа и операцию из командной строки:
Теперь можно запускать так:
Позиционные аргументы (
### Флаги и логические переключатели
Частая задача — включить «болтливый» режим:
Флаг без значения, просто включается при наличии в командной строке.
### Значения по умолчанию и типы
Попробуйте передать строку вместо числа — будет аккуратное сообщение с подсказкой по использованию.
### Подкоманды: один скрипт — много режимов
Когда скрипт начинает «разрастаться», удобно завести подкоманды, как у
Запуск:
Рано или поздно любой скрипт вырастает из стадии «запустить и забыть» и ему хочется передавать параметры: путь к файлу, режим работы, уровень детализации логов. Жестко прописывать это в коде неудобно – каждый раз править и перезапускать. Тут и появляется герой дня — модуль
argparse.### Базовый пример: скрипт-калькулятор
Сделаем простой калькулятор, который принимает числа и операцию из командной строки:
import argparse
def main():
parser = argparse.ArgumentParser(
description="Simple CLI calculator"
)
parser.add_argument("x", type=float, help="First number")
parser.add_argument("y", type=float, help="Second number")
parser.add_argument(
"--op", "-o",
choices=["add", "sub", "mul", "div"],
default="add",
help="Operation to perform"
)
args = parser.parse_args()
if args.op == "add":
result = args.x + args.y
elif args.op == "sub":
result = args.x - args.y
elif args.op == "mul":
result = args.x * args.y
elif args.op == "div":
result = args.x / args.y
print(result)
if __name__ == "__main__":
main()
Теперь можно запускать так:
python calc.py 10 5 --op mul
# 50.0
Позиционные аргументы (
x, y) обязательны, опция --op имеет значение по умолчанию и подсказку.### Флаги и логические переключатели
Частая задача — включить «болтливый» режим:
import argparse
parser = argparse.ArgumentParser(description="Demo for flags")
parser.add_argument("--verbose", "-v", action="store_true",
help="Enable verbose mode")
args = parser.parse_args()
if args.verbose:
print("Verbose mode is ON")
Флаг без значения, просто включается при наличии в командной строке.
### Значения по умолчанию и типы
argparse сам преобразует типы и выдаст внятную ошибку, если пользователь введет ерунду:parser.add_argument(
"--limit",
type=int,
default=10,
help="Max number of items (default: 10)"
)
Попробуйте передать строку вместо числа — будет аккуратное сообщение с подсказкой по использованию.
### Подкоманды: один скрипт — много режимов
Когда скрипт начинает «разрастаться», удобно завести подкоманды, как у
git (git add, git commit):import argparse
parser = argparse.ArgumentParser(description="User manager")
subparsers = parser.add_subparsers(dest="command", required=True)
add_parser = subparsers.add_parser("add", help="Add new user")
add_parser.add_argument("name")
add_parser.add_argument("--admin", action="store_true")
list_parser = subparsers.add_parser("list", help="List users")
args = parser.parse_args()
if args.command == "add":
print(f"Adding user {args.name}, admin={args.admin}")
elif args.command == "list":
print("Listing users...")
Запуск:
python usertool.py add Alice --admin
python usertool.py list
argparse берет на себя всю грязную работу по разбору аргументов, проверке типов и красивой справке. А вы концентрируетесь на логике программы, а не на парсинге строк.👍2
Как использовать
Если вам кажется, что для анализа данных нужны сложные библиотеки,
---
### Что такое
---
### Анализ текста: частота слов и букв
Классический пример — быстрый анализ текста.
Метод
---
### Подсчёт категорий: простой пример «аналитики»
Допустим, у вас есть список заказов с типами товаров:
Без циклов и лишнего кода вы уже понимаете распределение по категориям.
---
### Операции над
Так можно быстро сравнивать периоды, версии данных или сегменты пользователей.
---
### Фильтрация и преобразование результата
---
collections.Counter для простого анализа данныхЕсли вам кажется, что для анализа данных нужны сложные библиотеки,
Counter из модуля collections готов поспорить. Это маленький, но очень полезный инструмент, который превращает обычные списки и строки в наглядную статистику.---
### Что такое
CounterCounter — это специальный словарь для подсчёта количества элементов. Он считает, сколько раз встречается каждый объект, и делает это очень удобно.from collections import Counter
data = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
counter = Counter(data)
print(counter) # Counter({'apple': 3, 'banana': 2, 'orange': 1})
print(counter['apple']) # 3
Counter сразу выдаёт вам частоты элементов. Можно думать о нём как об «умном» словаре для статистики.---
### Анализ текста: частота слов и букв
Классический пример — быстрый анализ текста.
from collections import Counter
text = "data analysis with python and data tools"
words = text.split()
word_freq = Counter(words)
char_freq = Counter(text.replace(" ", ""))
print(word_freq.most_common(3)) # топ-3 самых частых слов
print(char_freq.most_common(5)) # топ-5 самых частых символов
Метод
.most_common(n) возвращает n самых популярных элементов — готовая мини-аналитика.---
### Подсчёт категорий: простой пример «аналитики»
Допустим, у вас есть список заказов с типами товаров:
from collections import Counter
orders = [
"book", "book", "laptop", "phone",
"book", "phone", "tablet", "laptop"
]
category_stats = Counter(orders)
print(category_stats) # сколько каких товаров заказали
print(category_stats.most_common(1)) # самый популярный товар
Без циклов и лишнего кода вы уже понимаете распределение по категориям.
---
### Операции над
Counter: как с многомерными счётчикамиCounter умеет складывать, вычитать и объединять данные — удобно для сравнения наборов.from collections import Counter
week1 = Counter(['book', 'book', 'phone'])
week2 = Counter(['book', 'tablet', 'phone', 'phone'])
print(week1 + week2) # суммарные продажи
print(week2 - week1) # что «добавилось» во второй неделе
Так можно быстро сравнивать периоды, версии данных или сегменты пользователей.
---
### Фильтрация и преобразование результата
Counter легко превратить в обычный словарь и отфильтровать по условию:from collections import Counter
data = ['a', 'b', 'a', 'c', 'b', 'a', 'd']
c = Counter(data)
freq = {k: v for k, v in c.items() if v > 1}
print(freq) # {'a': 3, 'b': 2}
---
collections.Counter — отличный инструмент, чтобы «понюхать данные»: быстро понять, что в них чаще всего встречается, найти лидеров и выбросы. И всё это в пару строк кода, без тяжёлых библиотек и сложной математики.👍3
Создание PDF-документов с помощью FPDF: просто, как print()
PDF — идеальный формат для чеков, отчетов, сертификатов и даже мини-отчетов по учебным проектам. В Python один из самых понятных инструментов для их создания — библиотека fpdf2 (современный форк FPDF).
---
### Установка
Базовый объект —
---
### Первый PDF за 10 строк
Создадим простой одностраничный документ с заголовком и текстом.
Ключевые моменты:
-
-
-
-
-
---
### Простой “отчет”: таблица в PDF
Сделаем мини-таблицу, например, список задач.
Здесь:
- Используем список кортежей как источник данных.
- Меняем стиль шрифта для заголовка таблицы.
-
---
### Добавление изображения
Например, логотип в шапке:
---
FPDF удобна тем, что работает “как конструктор”: просто добавляете текст, таблицы, картинки, постепенно превращая Python-скрипт в генератор аккуратных PDF-документов. Отличный навык для автоматизации отчетов и учебных проектов.
PDF — идеальный формат для чеков, отчетов, сертификатов и даже мини-отчетов по учебным проектам. В Python один из самых понятных инструментов для их создания — библиотека fpdf2 (современный форк FPDF).
---
### Установка
pip install fpdf2
Базовый объект —
FPDF, с ним и будем работать.---
### Первый PDF за 10 строк
Создадим простой одностраничный документ с заголовком и текстом.
from fpdf import FPDF
pdf = FPDF()
pdf.add_page()
pdf.set_font("Arial", size=16)
pdf.cell(0, 10, "Hello, PDF world!", ln=1, align="C")
pdf.set_font("Arial", size=12)
text = "This is a simple PDF generated with fpdf2 in Python."
pdf.multi_cell(0, 8, text)
pdf.output("example_basic.pdf")
Ключевые моменты:
-
add_page() — добавляем страницу.-
set_font() — выбираем шрифт и размер.-
cell() — строка текста; ln=1 переносит курсор на новую строку.-
multi_cell() — блок текста с переносами.-
output() — сохраняем файл.---
### Простой “отчет”: таблица в PDF
Сделаем мини-таблицу, например, список задач.
from fpdf import FPDF
data = [
("ID", "Task", "Status"),
("1", "Learn FPDF basics", "Done"),
("2", "Generate simple report", "In progress"),
("3", "Share PDF with team", "Pending"),
]
pdf = FPDF()
pdf.add_page()
pdf.set_font("Arial", "B", 14)
pdf.cell(0, 10, "Tasks Report", ln=1, align="C")
pdf.ln(4)
pdf.set_font("Arial", size=11)
col_widths = [15, 90, 30]
for row_index, row in enumerate(data):
for col_index, text in enumerate(row):
style = "B" if row_index == 0 else ""
pdf.set_font("Arial", style, 11)
pdf.cell(col_widths[col_index], 8, text, border=1)
pdf.ln(8)
pdf.output("tasks_report.pdf")
Здесь:
- Используем список кортежей как источник данных.
- Меняем стиль шрифта для заголовка таблицы.
-
border=1 рисует рамки ячеек — уже похоже на отчет.---
### Добавление изображения
Например, логотип в шапке:
from fpdf import FPDF
pdf = FPDF()
pdf.add_page()
pdf.image("logo.png", x=10, y=8, w=30)
pdf.set_font("Arial", "B", 16)
pdf.cell(0, 10, "Company Report", ln=1, align="C")
pdf.output("report_with_logo.pdf")
---
FPDF удобна тем, что работает “как конструктор”: просто добавляете текст, таблицы, картинки, постепенно превращая Python-скрипт в генератор аккуратных PDF-документов. Отличный навык для автоматизации отчетов и учебных проектов.
👍3❤1
Введение в работу с INI‑файлами с модулем
Когда настройки программы начинают расползаться по коду в виде "магических констант", наступает хаос. Гораздо удобнее хранить конфиг в отдельном файле — компактно, читаемо и без лишних зависимостей. Для этого в Python есть стандартный модуль
### Как выглядит INI‑файл
Типичный
Секции в квадратных скобках, ниже — пары
### Чтение INI с
Полезные методы:
-
-
- квадратные скобки
### Значения по умолчанию
Если нужного ключа нет — можно задать дефолт:
Так код не упадет, даже если в INI забыли прописать опцию.
### Создание и запись INI‑файла
Секции и ключи ведут себя почти как обычные словари.
### Переменные и интерполяция
Если интерполяция не нужна, можно выключить:
---
configparserКогда настройки программы начинают расползаться по коду в виде "магических констант", наступает хаос. Гораздо удобнее хранить конфиг в отдельном файле — компактно, читаемо и без лишних зависимостей. Для этого в Python есть стандартный модуль
configparser, который работает с INI‑файлами.### Как выглядит INI‑файл
Типичный
settings.ini:[database]
host = localhost
port = 5432
user = admin
[app]
debug = true
log_level = INFO
Секции в квадратных скобках, ниже — пары
ключ = значение.### Чтение INI с
configparserimport 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)
Полезные методы:
-
get() — строка (по умолчанию)-
getint(), getfloat(), getboolean() — автоматическое приведение типов- квадратные скобки
config["section"]["key"] — быстрый доступ как к словарю### Значения по умолчанию
Если нужного ключа нет — можно задать дефолт:
log_level = config.get("app", "log_level", fallback="WARNING")
Так код не упадет, даже если в INI забыли прописать опцию.
### Создание и запись INI‑файла
import configparser
config = configparser.ConfigParser()
config["database"] = {
"host": "localhost",
"port": "3306",
"user": "root"
}
config["app"] = {}
config["app"]["debug"] = "false"
config["app"]["log_level"] = "INFO"
with open("generated.ini", "w") as config_file:
config.write(config_file)
Секции и ключи ведут себя почти как обычные словари.
### Переменные и интерполяция
configparser умеет подставлять значения из других опций:[paths]
base_dir = /var/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) # /var/app/logs
Если интерполяция не нужна, можно выключить:
config = configparser.ConfigParser(interpolation=None)
---
configparser — простой способ вынести настройки из кода, не таща в проект JSON/YAML и сторонние библиотеки. Для маленьких и средних приложений INI‑файл нередко оказывается приятным и достаточным форматом конфигурации.👍3
Создаем простой чат-сервер на сокетах: от нуля до мини-мессенджера
Сокеты — это базовый способ общения программ по сети. Без них не было бы ни браузеров, ни мессенджеров, ни онлайн-игр. Давай сделаем свой простой чат-сервер на Python и поймем, как это работает изнутри.
---
## Базовая идея
Нам нужны две части:
1. Сервер — ждет подключения клиентов и пересылает сообщения.
2. Клиент — подключается к серверу и отправляет/получает сообщения.
Используем стандартный модуль
---
## Простой сервер
Этот сервер:
- принимает несколько клиентов,
- читает от каждого сообщения,
- рассылает их всем остальным.
Ключевые моменты:
-
-
- Для каждого клиента создаем поток, чтобы они не блокировали друг друга.
---
## Простой клиент
Подключаемся к серверу и читаем/пишем сообщения одновременно.
---
## Что можно улучшить дальше
- Добавить никнеймы и формат сообщений (
- Логировать чаты в файл.
- Использовать
Но уже сейчас ты сделал основу своего мини-мессенджера на чистых сокетах — без магии, только протокол TCP и немного кода.
Сокеты — это базовый способ общения программ по сети. Без них не было бы ни браузеров, ни мессенджеров, ни онлайн-игр. Давай сделаем свой простой чат-сервер на Python и поймем, как это работает изнутри.
---
## Базовая идея
Нам нужны две части:
1. Сервер — ждет подключения клиентов и пересылает сообщения.
2. Клиент — подключается к серверу и отправляет/получает сообщения.
Используем стандартный модуль
socket, ничего дополнительно устанавливать не нужно.---
## Простой сервер
Этот сервер:
- принимает несколько клиентов,
- читает от каждого сообщения,
- рассылает их всем остальным.
import socket
import threading
HOST = "127.0.0.1"
PORT = 5000
clients = []
def broadcast(message, sender_sock):
for client in clients:
if client is not sender_sock:
try:
client.sendall(message)
except OSError:
pass
def handle_client(client_sock, addr):
print(f"Connected: {addr}")
while True:
try:
data = client_sock.recv(1024)
if not data:
break
broadcast(data, client_sock)
except ConnectionResetError:
break
print(f"Disconnected: {addr}")
clients.remove(client_sock)
client_sock.close()
def run_server():
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.bind((HOST, PORT))
server_sock.listen()
print(f"Server listening on {HOST}:{PORT}")
while True:
client_sock, addr = server_sock.accept()
clients.append(client_sock)
thread = threading.Thread(target=handle_client, args=(client_sock, addr), daemon=True)
thread.start()
if __name__ == "__main__":
run_server()
Ключевые моменты:
-
socket.AF_INET, socket.SOCK_STREAM — TCP-соединение по IPv4.-
listen() — сервер готов принимать соединения.- Для каждого клиента создаем поток, чтобы они не блокировали друг друга.
---
## Простой клиент
Подключаемся к серверу и читаем/пишем сообщения одновременно.
import socket
import threading
HOST = "127.0.0.1"
PORT = 5000
def receive_messages(sock):
while True:
try:
data = sock.recv(1024)
if not data:
print("Connection closed by server")
break
print(">>", data.decode("utf-8"))
except OSError:
break
def run_client():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((HOST, PORT))
print("Connected to chat server")
thread = threading.Thread(target=receive_messages, args=(sock,), daemon=True)
thread.start()
try:
while True:
msg = input()
if msg.lower() == "/quit":
break
sock.sendall(msg.encode("utf-8"))
finally:
sock.close()
if __name__ == "__main__":
run_client()
---
## Что можно улучшить дальше
- Добавить никнеймы и формат сообщений (
[user]: text).- Логировать чаты в файл.
- Использовать
asyncio вместо потоков для масштабируемости.Но уже сейчас ты сделал основу своего мини-мессенджера на чистых сокетах — без магии, только протокол TCP и немного кода.
👍3