Основы работы с Memcached и Redis для кэширования данных
Если ваш Python‑код постоянно лезет в базу за одними и теми же данными — вы жжёте процессор и тормозите приложение. В таких случаях на сцену выходят Memcached и Redis — быстрые in‑memory хранилища, идеальные для кэширования.
---
### Зачем нужен кэш
Кэширование — это сохранение заранее вычисленных результатов в память, чтобы не пересчитывать и не запрашивать их повторно. Типичный сценарий:
1. Проверяем кэш.
2. Если данных нет — обращаемся к БД / API.
3. Кладём результат в кэш.
4. В следующий раз берём из кэша за миллисекунды.
---
### Memcached: минималистичный и быстрый
Memcached — это просто распределённый словарь в памяти: ключ–значение, без сложных типов данных.
Установка клиента:
Пример:
Особенности Memcached:
- Нет персистентности: перезапустили — всё забыто.
- Простые типы (строки/байты).
- Идеален как быстрый временный кэш поверх БД.
---
### Redis: швейцарский нож кэширования
Redis умеет больше: строки, списки, множества, хэши, TTL, pub/sub, транзакции и персистентность.
Установка:
Пример кэша с TTL:
Пример использования сложных структур: счётчик просмотров товара:
---
### Что выбрать новичку
- Memcached, если нужен простой, сверхбыстрый кэш без сложных структур и персистентности.
- Redis, если хотите кэш + дополнительные возможности (счётчики, очереди, сессии, персистентность).
Для учебных проектов Redis часто удобнее: он универсален и его легче масштабировать по мере усложнения системы.
Если ваш Python‑код постоянно лезет в базу за одними и теми же данными — вы жжёте процессор и тормозите приложение. В таких случаях на сцену выходят Memcached и Redis — быстрые in‑memory хранилища, идеальные для кэширования.
---
### Зачем нужен кэш
Кэширование — это сохранение заранее вычисленных результатов в память, чтобы не пересчитывать и не запрашивать их повторно. Типичный сценарий:
1. Проверяем кэш.
2. Если данных нет — обращаемся к БД / API.
3. Кладём результат в кэш.
4. В следующий раз берём из кэша за миллисекунды.
---
### Memcached: минималистичный и быстрый
Memcached — это просто распределённый словарь в памяти: ключ–значение, без сложных типов данных.
Установка клиента:
pip install pymemcache
Пример:
from pymemcache.client import base
client = base.Client(("localhost", 11211))
def get_user_profile(user_id: int) -> dict:
cache_key = f"user:{user_id}"
cached = client.get(cache_key)
if cached:
# данные хранятся как bytes; в реальном коде — json.loads(...)
print("from cache")
return eval(cached.decode("utf-8"))
print("from db")
user = {"id": user_id, "name": "Alice"} # тут должна быть реальная БД
client.set(cache_key, str(user).encode("utf-8"), expire=60)
return user
get_user_profile(1)
get_user_profile(1)
Особенности Memcached:
- Нет персистентности: перезапустили — всё забыто.
- Простые типы (строки/байты).
- Идеален как быстрый временный кэш поверх БД.
---
### Redis: швейцарский нож кэширования
Redis умеет больше: строки, списки, множества, хэши, TTL, pub/sub, транзакции и персистентность.
Установка:
pip install redis
Пример кэша с TTL:
import json
from redis import Redis
redis_client = Redis(host="localhost", port=6379, db=0)
def get_product(product_id: int) -> dict:
cache_key = f"product:{product_id}"
cached = redis_client.get(cache_key)
if cached:
print("from cache")
return json.loads(cached)
print("from db")
product = {"id": product_id, "title": "Keyboard", "price": 99.9}
redis_client.set(cache_key, json.dumps(product), ex=120)
return product
get_product(10)
get_product(10)
Пример использования сложных структур: счётчик просмотров товара:
def increment_views(product_id: int) -> int:
key = f"product:{product_id}:views"
return redis_client.incr(key)
print(increment_views(10))
print(increment_views(10))
---
### Что выбрать новичку
- Memcached, если нужен простой, сверхбыстрый кэш без сложных структур и персистентности.
- Redis, если хотите кэш + дополнительные возможности (счётчики, очереди, сессии, персистентность).
Для учебных проектов Redis часто удобнее: он универсален и его легче масштабировать по мере усложнения системы.
👍2
Использование pandas для преобразования и очистки данных
Если вы хоть раз открывали «сырой» CSV-файл, то знаете: данные редко бывают аккуратными. Пропуски, дубли, странные форматы дат — всё это мешает анализу. Здесь на сцену выходит
---
### Загрузка и первый взгляд на данные
Начнём с простого: прочитаем CSV и посмотрим, что внутри.
---
### Преобразование типов и работа с датами
Частая проблема — строки там, где должны быть числа или даты.
Параметр
---
### Очистка пропусков и дубликатов
Пропуски можно либо удалить, либо заполнить осмысленными значениями.
Для числовых столбцов часто используют среднее, медиану или 0 — зависит от задачи:
---
### Создание новых признаков
Pandas позволяет легко добавлять столбцы, основанные на уже существующих.
За пару строк кода можно превратить «сырые» данные в удобный набор признаков для анализа или модели.
---
### Фильтрация и группировка
Комбинация фильтрации и группировки — основа исследовательского анализа.
С помощью
---
Если вы хоть раз открывали «сырой» CSV-файл, то знаете: данные редко бывают аккуратными. Пропуски, дубли, странные форматы дат — всё это мешает анализу. Здесь на сцену выходит
pandas — швейцарский нож для работы с табличными данными в Python.---
### Загрузка и первый взгляд на данные
Начнём с простого: прочитаем CSV и посмотрим, что внутри.
import pandas as pd
df = pd.read_csv("sales.csv")
print(df.head())
print(df.info())
head() показывает первые строки, info() — типы столбцов и наличие пропусков. Уже отсюда часто можно понять, где бардак: например, даты хранятся как object, а суммы — как строки.---
### Преобразование типов и работа с датами
Частая проблема — строки там, где должны быть числа или даты.
df["amount"] = pd.to_numeric(df["amount"], errors="coerce")
df["date"] = pd.to_datetime(df["date"], errors="coerce")
Параметр
errors="coerce" превращает невозможные значения в NaN. Это удобно: дальше их можно явно обработать, а не ловить таинственные ошибки.---
### Очистка пропусков и дубликатов
Пропуски можно либо удалить, либо заполнить осмысленными значениями.
# Удаляем строки, где нет суммы покупки
df = df.dropna(subset=["amount"])
# Заполняем пропущенный город значением "Unknown"
df["city"] = df["city"].fillna("Unknown")
# Удаляем полные дубликаты строк
df = df.drop_duplicates()
Для числовых столбцов часто используют среднее, медиану или 0 — зависит от задачи:
df["discount"] = df["discount"].fillna(df["discount"].median())
---
### Создание новых признаков
Pandas позволяет легко добавлять столбцы, основанные на уже существующих.
df["year"] = df["date"].dt.year
df["month"] = df["date"].dt.month
df["amount_with_tax"] = df["amount"] * 1.2
За пару строк кода можно превратить «сырые» данные в удобный набор признаков для анализа или модели.
---
### Фильтрация и группировка
Комбинация фильтрации и группировки — основа исследовательского анализа.
# Оставим только заказы после 2023 года и с положительной суммой
filtered = df[(df["date"].dt.year >= 2023) & (df["amount"] > 0)]
# Посчитаем суммарные продажи по городам
city_stats = (
filtered
.groupby("city", as_index=False)["amount"]
.sum()
.rename(columns={"amount": "total_amount"})
)
print(city_stats.head())
С помощью
groupby и агрегирующих функций (sum, mean, count и др.) можно быстро получать сводки и отчёты, не трогая Excel.---
pandas превращает хаотичный CSV в аккуратный, структурированный датафрейм, с которым приятно работать. Чем раньше вы начнёте его использовать, тем меньше времени будете тратить на рутину и тем больше — на сам анализ.👍1
Как настроить асинхронные HTTP‑запросы с aiohttp
Синхронный код в стиле
---
### Установка и базовый пример
Первый асинхронный запрос:
Разбор:
-
-
-
---
### Параллельные запросы
Самая вкусная часть — запуск запросов одновременно:
Все запросы уходят почти одновременно, а ответы обрабатываются по мере готовности. Разница особенно заметна на “медленных” API.
---
### Таймауты и обработка ошибок
Без ограничений можно повесить программу на “вечном” запросе. Добавим таймаут и отловим ошибки:
Ключевые моменты:
-
-
- перехват
---
### Когда стоит переходить на aiohttp
Используйте асинхронные HTTP‑запросы, когда:
- нужно дернуть много URL в короткое время;
- ваш код в основном ждет сети или диска;
- вы пишете асинхронный веб‑скрейпер, бота или микросервис.
Если запрос один‑два —
Синхронный код в стиле
requests.get() удобен, пока запросов мало. Но как только нужно опросить десятки или сотни URL — программа начинает “залипать”. Здесь на сцену выходит aiohttp и asyncio.---
### Установка и базовый пример
pip install aiohttp
Первый асинхронный запрос:
import asyncio
import aiohttp
async def fetch(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(session, "https://httpbin.org/get")
print(len(html))
asyncio.run(main())
Разбор:
-
async def — объявление корутины;-
await — “подождать, но не блокировать”;-
ClientSession — переиспользует соединения (это важно для производительности).---
### Параллельные запросы
Самая вкусная часть — запуск запросов одновременно:
import asyncio
import aiohttp
URLS = [
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/2",
"https://httpbin.org/delay/3",
]
async def fetch_status(session, url):
async with session.get(url) as response:
return url, response.status
async def main():
async with aiohttp.ClientSession() as session:
tasks = [asyncio.create_task(fetch_status(session, url)) for url in URLS]
for task in asyncio.as_completed(tasks):
url, status = await task
print(url, "->", status)
asyncio.run(main())
Все запросы уходят почти одновременно, а ответы обрабатываются по мере готовности. Разница особенно заметна на “медленных” API.
---
### Таймауты и обработка ошибок
Без ограничений можно повесить программу на “вечном” запросе. Добавим таймаут и отловим ошибки:
import asyncio
import aiohttp
from aiohttp import ClientError
async def safe_fetch_json(session, url):
try:
async with session.get(url, timeout=3) as response:
response.raise_for_status()
return await response.json()
except asyncio.TimeoutError:
print("Timeout:", url)
except ClientError as e:
print("ClientError:", url, "->", e)
async def main():
async with aiohttp.ClientSession() as session:
data = await safe_fetch_json("https://httpbin.org/json")
print("Result:", data)
asyncio.run(main())
Ключевые моменты:
-
timeout=3 — запрос не будет длиться бесконечно;-
raise_for_status() — выбросит исключение при 4xx/5xx;- перехват
ClientError — базовый класс большинства сетевых ошибок в aiohttp.---
### Когда стоит переходить на aiohttp
Используйте асинхронные HTTP‑запросы, когда:
- нужно дернуть много URL в короткое время;
- ваш код в основном ждет сети или диска;
- вы пишете асинхронный веб‑скрейпер, бота или микросервис.
Если запрос один‑два —
requests проще. Но как только вы упираетесь в “ожидание сети”, aiohttp + asyncio дает очень ощутимый прирост скорости, не усложняя код слишком сильно.❤3
Создание и управление новой базой данных SQLite в Python
SQLite — идеальная отправная точка для тех, кто хочет начать работать с базами данных, не устанавливая сервер и не настраивая сложные системы. Всё хранится в одном файле, а в Python уже есть встроенный модуль
Разберём базовые шаги: создание БД, таблиц, вставка, выборка и обновление данных.
---
### 1. Подключение и создание базы
Если файла базы данных нет,
---
### 2. Создание таблицы
Создадим таблицу для статей блога:
---
### 3. Вставка данных
Добавим новую запись:
Обрати внимание на
---
### 4. Чтение данных
Вытащим все статьи:
---
### 5. Обновление и удаление
Изменим заголовок и удалим запись:
---
### 6. Акуратное закрытие соединения
Либо использовать контекстный менеджер:
В этом случае
---
SQLite +
SQLite — идеальная отправная точка для тех, кто хочет начать работать с базами данных, не устанавливая сервер и не настраивая сложные системы. Всё хранится в одном файле, а в Python уже есть встроенный модуль
sqlite3.Разберём базовые шаги: создание БД, таблиц, вставка, выборка и обновление данных.
---
### 1. Подключение и создание базы
Если файла базы данных нет,
sqlite3 создаст его автоматически:import sqlite3
conn = sqlite3.connect("blog.db") # создаем/открываем файл базы
cursor = conn.cursor()
conn — это соединение, cursor — объект для выполнения SQL-запросов.---
### 2. Создание таблицы
Создадим таблицу для статей блога:
cursor.execute("""
CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
""")
conn.commit()
IF NOT EXISTS защитит от ошибки при повторном запуске скрипта.---
### 3. Вставка данных
Добавим новую запись:
new_post = ("First post", "This is my first post content.")
cursor.execute(
"INSERT INTO posts (title, content) VALUES (?, ?)",
new_post
)
conn.commit()
Обрати внимание на
? — это параметризованный запрос. Так безопаснее и защищает от SQL-инъекций.---
### 4. Чтение данных
Вытащим все статьи:
cursor.execute("SELECT id, title, created_at FROM posts")
rows = cursor.fetchall()
for row in rows:
post_id, title, created_at = row
print(post_id, title, created_at)
fetchall() возвращает список кортежей. Для больших объёмов данных выгоднее использовать fetchone() в цикле.---
### 5. Обновление и удаление
Изменим заголовок и удалим запись:
cursor.execute(
"UPDATE posts SET title = ? WHERE id = ?",
("Updated title", 1)
)
cursor.execute(
"DELETE FROM posts WHERE id = ?",
(2,)
)
conn.commit()
---
### 6. Акуратное закрытие соединения
conn.close()
Либо использовать контекстный менеджер:
import sqlite3
with sqlite3.connect("blog.db") as conn:
cursor = conn.cursor()
cursor.execute("SELECT COUNT(*) FROM posts")
print(cursor.fetchone())
В этом случае
commit() и close() произойдут автоматически.---
SQLite +
sqlite3 — это минимальный, но мощный набор, чтобы почувствовать себя создателем настоящих приложений с базой данных: от простого блога до небольших утилит и заметочников. Главное — аккуратная работа с запросами и понимание, что всё это уже доступно «из коробки» в Python.👍2
Введение в управление сессиями пользователей с помощью Flask
Когда вы пишете веб‑приложение, быстро возникает вопрос: как «запомнить» пользователя между запросами? HTTP сам по себе «ничего не помнит» — каждый запрос живет отдельно. Здесь на сцену выходят сессии.
Во Flask сессия — это обычный словарь
Начнем с минимального примера «ручного входа»:
Что здесь важно:
-
-
-
-
Flask хранит содержимое сессии в зашифрованном cookie, поэтому:
1. Не кладите туда большие данные (лимит cookie обычно до 4 КБ).
2. Не храните критически важные секреты (например, пароли в открытом виде).
3. Обязательно ставьте нормальный
Чтобы чуть усложнить пример, добавим флаг администратора:
Так шаг за шагом можно строить авторизацию, роли, корзину покупок, настройки пользователя — всё это основано на сессиях. Flask делает работу с ними максимально простой: по сути вы просто модифицируете словарь, а фреймворк сам связывает его с конкретным пользователем.
Когда вы пишете веб‑приложение, быстро возникает вопрос: как «запомнить» пользователя между запросами? HTTP сам по себе «ничего не помнит» — каждый запрос живет отдельно. Здесь на сцену выходят сессии.
Во Flask сессия — это обычный словарь
session, который привязан к пользователю через cookie. Данные сессии шифруются с помощью SECRET_KEY, поэтому значение ключа должно быть сложным и храниться в секрете.Начнем с минимального примера «ручного входа»:
from flask import Flask, session, redirect, url_for, request
app = Flask(__name__)
app.secret_key = "change_this_secret_key"
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form.get("username")
# Здесь могла бы быть проверка пароля
session["user"] = username
return redirect(url_for("profile"))
return """
<form method="post">
<input name="username" placeholder="Username">
<button type="submit">Login</button>
</form>
"""
@app.route("/profile")
def profile():
user = session.get("user")
if not user:
return redirect(url_for("login"))
return f"Hello, {user}! This is your profile."
@app.route("/logout")
def logout():
session.pop("user", None)
return redirect(url_for("login"))
if __name__ == "__main__":
app.run(debug=True)
Что здесь важно:
-
session работает почти как обычный словарь.-
session["user"] = username — «залогинили» пользователя.-
session.get("user") — проверяем, авторизован ли он.-
session.pop("user", None) — «разлогин».Flask хранит содержимое сессии в зашифрованном cookie, поэтому:
1. Не кладите туда большие данные (лимит cookie обычно до 4 КБ).
2. Не храните критически важные секреты (например, пароли в открытом виде).
3. Обязательно ставьте нормальный
SECRET_KEY (случайную строку, а не 123).Чтобы чуть усложнить пример, добавим флаг администратора:
@app.route("/set_admin")
def set_admin():
session["is_admin"] = True
return "Admin mode enabled"
@app.route("/admin")
def admin_panel():
if not session.get("is_admin"):
return "Access denied", 403
return "Welcome to admin panel"
Так шаг за шагом можно строить авторизацию, роли, корзину покупок, настройки пользователя — всё это основано на сессиях. Flask делает работу с ними максимально простой: по сути вы просто модифицируете словарь, а фреймворк сам связывает его с конкретным пользователем.
👍1
Управление Docker-контейнерами с Python через docker-py
Когда проекты перестают помещаться в одну виртуалку, на сцену выходит Docker. Но почему бы не управлять контейнерами прямо из Python-скрипта, а не вручную через консоль? Для этого есть библиотека docker-py (официальный Docker SDK для Python).
---
### Установка и подключение
Простейшее подключение:
---
### Запуск контейнера одной строкой
Запустим контейнер с
Что происходит:
-
-
-
---
### Список и состояние контейнеров
Пара полезных статусов:
Остановить и удалить контейнер по имени:
---
### Запуск команды внутри контейнера
Представьте, что нужно выполнить миграции или тесты внутри образа:
---
### Управление образами
Скачать образ, если его еще нет:
Удалить неиспользуемый образ:
---
### Где это пригодится
- написание своих mini-оркестраторов и утилит;
- автоматизация локальной разработки (запустить БД, брокер, кэш одной командой);
- тестирование в изолированном окружении с разными версиями Python и библиотек.
Когда проекты перестают помещаться в одну виртуалку, на сцену выходит Docker. Но почему бы не управлять контейнерами прямо из Python-скрипта, а не вручную через консоль? Для этого есть библиотека docker-py (официальный Docker SDK для Python).
---
### Установка и подключение
pip install docker
Простейшее подключение:
import docker
client = docker.from_env()
from_env() находит Docker-демон по переменным окружения (обычно это сокет /var/run/docker.sock).---
### Запуск контейнера одной строкой
Запустим контейнер с
nginx в фоне:import docker
client = docker.from_env()
container = client.containers.run(
"nginx:latest",
detach=True,
ports={"80/tcp": 8080},
name="my_nginx"
)
print(container.id)
Что происходит:
-
detach=True — контейнер не блокирует скрипт;-
ports пробрасывает 80 порт контейнера на 8080 хоста;-
name задает читаемое имя, чтобы не искать по ID.---
### Список и состояние контейнеров
containers = client.containers.list(all=True)
for c in containers:
print(c.name, c.status)
Пара полезных статусов:
running, exited, created.Остановить и удалить контейнер по имени:
container = client.containers.get("my_nginx")
container.stop()
container.remove()
---
### Запуск команды внутри контейнера
Представьте, что нужно выполнить миграции или тесты внутри образа:
container = client.containers.run(
"python:3.12-slim",
command="python -c \"print('Hello from container')\"",
detach=True
)
logs = container.logs().decode("utf-8")
print(logs)
container.remove()
logs() возвращает вывод процесса — можно использовать для автоматизации CI/CD.---
### Управление образами
Скачать образ, если его еще нет:
image = client.images.pull("redis:7-alpine")
print(image.tags)
Удалить неиспользуемый образ:
client.images.remove("redis:7-alpine")
---
### Где это пригодится
- написание своих mini-оркестраторов и утилит;
- автоматизация локальной разработки (запустить БД, брокер, кэш одной командой);
- тестирование в изолированном окружении с разными версиями Python и библиотек.
docker-py превращает Docker в обычную Python-библиотеку: вместо больших bash-скриптов — компактные и читаемые Python-программы.❤3
Python для начинающих: ускоряем загрузку данных с помощью многопоточности
Один файл скачать — не проблема. Десять — уже раздражает. Сто, тысяча — и ваш скрипт начинает жить своей жизнью, а вы смотрите, как он медленно тянет данные по одному запросу. Исправляем это с помощью многопоточности.
### Когда имеет смысл использовать потоки?
В Python потоки отлично подходят для задач, которые ждут сеть или диск, а не процессор:
— скачивание файлов;
— запросы к API;
— парсинг веб-страниц.
Ограничение GIL мешает распараллеливать чистые вычисления, но для сетевых операций потоки дают реальный прирост.
### Базовый вариант:
Сначала — наивный однопоточный код:
Каждый запрос ждет завершения предыдущего. Время ≈ сумме задержек.
Теперь включим многопоточность:
Что изменилось:
-
-
-
-
Теперь общее время будет примерно равно самому «длинному» запросу, а не сумме всех.
### Как выбрать
Небольшое правило для сетевых задач:
Слишком мало — медленно. Слишком много — забьете сеть или получите лимиты от API.
### Скачивание и сохранение файлов
Минимальный пример, который реально полезен:
Здесь
---
Многопоточность для загрузки данных — это простой способ превратить медленный последовательный скрипт в уверенный «пулевой поезд». Главное — использовать ее там, где программа ждет сеть, а не считает числа.
Один файл скачать — не проблема. Десять — уже раздражает. Сто, тысяча — и ваш скрипт начинает жить своей жизнью, а вы смотрите, как он медленно тянет данные по одному запросу. Исправляем это с помощью многопоточности.
### Когда имеет смысл использовать потоки?
В Python потоки отлично подходят для задач, которые ждут сеть или диск, а не процессор:
— скачивание файлов;
— запросы к API;
— парсинг веб-страниц.
Ограничение GIL мешает распараллеливать чистые вычисления, но для сетевых операций потоки дают реальный прирост.
### Базовый вариант:
requests + ThreadPoolExecutorСначала — наивный однопоточный код:
import requests
urls = [
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/2",
"https://httpbin.org/delay/3",
]
def fetch(url: str) -> str:
resp = requests.get(url, timeout=5)
return f"{url} -> {resp.status_code}"
for url in urls:
print(fetch(url))
Каждый запрос ждет завершения предыдущего. Время ≈ сумме задержек.
Теперь включим многопоточность:
from concurrent.futures import ThreadPoolExecutor, as_completed
import requests
urls = [
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/2",
"https://httpbin.org/delay/3",
]
def fetch(url: str) -> str:
resp = requests.get(url, timeout=5)
return f"{url} -> {resp.status_code}"
with ThreadPoolExecutor(max_workers=5) as executor:
future_to_url = {executor.submit(fetch, url): url for url in urls}
for future in as_completed(future_to_url):
print(future.result())
Что изменилось:
-
ThreadPoolExecutor создает пул потоков. -
max_workers — максимальное число параллельных задач. -
executor.submit планирует выполнение функции fetch. -
as_completed позволяет обрабатывать результаты по мере готовности.Теперь общее время будет примерно равно самому «длинному» запросу, а не сумме всех.
### Как выбрать
max_workers?Небольшое правило для сетевых задач:
max_workers ≈ 5–20 для начала. Слишком мало — медленно. Слишком много — забьете сеть или получите лимиты от API.
### Скачивание и сохранение файлов
Минимальный пример, который реально полезен:
from concurrent.futures import ThreadPoolExecutor
import requests
from pathlib import Path
urls = [
"https://httpbin.org/image/png",
"https://httpbin.org/image/jpeg",
]
output_dir = Path("downloads")
output_dir.mkdir(exist_ok=True)
def download_file(url: str) -> str:
resp = requests.get(url, timeout=10)
resp.raise_for_status()
filename = output_dir / url.split("/")[-1]
with open(filename, "wb") as f:
f.write(resp.content)
return f"Saved: {filename}"
with ThreadPoolExecutor(max_workers=4) as executor:
for result in executor.map(download_file, urls):
print(result)
Здесь
executor.map удобен, когда нужно просто применить одну функцию ко множеству аргументов и собрать все результаты.---
Многопоточность для загрузки данных — это простой способ превратить медленный последовательный скрипт в уверенный «пулевой поезд». Главное — использовать ее там, где программа ждет сеть, а не считает числа.
👍2
Создание базового приложения для управления расписанием с визуализацией
Когда задачи и встречи начинают жить собственной жизнью, пора заводить им цифровой «зоопарк» — небольшое приложение для расписания. Сделаем его на Python: будем хранить события, отображать их в виде таблицы и визуализировать нагрузку по дням.
### Шаг 1. Структура данных для расписания
Начнем с простой модели: список словарей, где каждое событие — это дата, время и описание.
### Шаг 2. Текстовая «визуализация» в виде таблицы
Отсортируем события и красиво выведем их в консоли.
Уже похоже на мини-органайзер, но хочется наглядности.
### Шаг 3. Визуализация загрузки по дням с matplotlib
Теперь посчитаем, сколько событий в каждый день, и построим столбчатую диаграмму. Для этого пригодится модуль
Теперь видно, какие дни перегружены, а где можно добавить еще пару дел.
### Шаг 4. Куда развиваться дальше
Идеи для улучшения:
- сохранять расписание в
- добавлять фильтр по дате/ключевому слову;
- заменить консольный вывод на
- выделять цветом важные события на графике.
Так из нескольких десятков строк кода получается живое мини-приложение, которое уже помогает управлять временем и показывает картину загрузки глазами Python.
Когда задачи и встречи начинают жить собственной жизнью, пора заводить им цифровой «зоопарк» — небольшое приложение для расписания. Сделаем его на Python: будем хранить события, отображать их в виде таблицы и визуализировать нагрузку по дням.
### Шаг 1. Структура данных для расписания
Начнем с простой модели: список словарей, где каждое событие — это дата, время и описание.
from datetime import datetime
schedule = []
def add_event(date_str, time_str, title):
event_datetime = datetime.strptime(
f"{date_str} {time_str}", "%Y-%m-%d %H:%M"
)
schedule.append({
"datetime": event_datetime,
"title": title
})
add_event("2025-01-10", "09:00", "Morning meeting")
add_event("2025-01-10", "14:30", "Code review")
add_event("2025-01-11", "11:00", "Gym")
### Шаг 2. Текстовая «визуализация» в виде таблицы
Отсортируем события и красиво выведем их в консоли.
def print_schedule():
events_sorted = sorted(schedule, key=lambda e: e["datetime"])
print(f"{'Date':<12} {'Time':<6} Title")
print("-" * 40)
for e in events_sorted:
d = e["datetime"].strftime("%Y-%m-%d")
t = e["datetime"].strftime("%H:%M")
print(f"{d:<12} {t:<6} {e['title']}")
print_schedule()
Уже похоже на мини-органайзер, но хочется наглядности.
### Шаг 3. Визуализация загрузки по дням с matplotlib
Теперь посчитаем, сколько событий в каждый день, и построим столбчатую диаграмму. Для этого пригодится модуль
collections и библиотека matplotlib.from collections import Counter
import matplotlib.pyplot as plt
def plot_daily_load():
dates = [e["datetime"].date() for e in schedule]
counter = Counter(dates)
days = sorted(counter.keys())
counts = [counter[d] for d in days]
plt.bar([d.strftime("%Y-%m-%d") for d in days], counts, color="skyblue")
plt.xlabel("Date")
plt.ylabel("Events count")
plt.title("Schedule load by day")
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
plot_daily_load()
Теперь видно, какие дни перегружены, а где можно добавить еще пару дел.
### Шаг 4. Куда развиваться дальше
Идеи для улучшения:
- сохранять расписание в
json или sqlite3;- добавлять фильтр по дате/ключевому слову;
- заменить консольный вывод на
tkinter-интерфейс;- выделять цветом важные события на графике.
Так из нескольких десятков строк кода получается живое мини-приложение, которое уже помогает управлять временем и показывает картину загрузки глазами Python.
👍5
Создание собственных итераторов и генераторов: как и зачем
Если вы поняли, как работает
---
### Итераторы: ручной режим
Итератор — это объект, у которого есть два метода:
Создадим свой итератор, который выдает квадраты чисел от 1 до
Плюсы: полный контроль — можно кешировать, логировать, считать статистику.
Минусы: много шаблонного кода.
---
### Генераторы: автоматический режим
Генератор — это «упрощенный итератор». Достаточно функции с
Код компактнее, логика та же. Функция
---
### Где это полезно на практике?
1. Работа с большими данными
Можно обрабатывать гигантские файлы построчно, не загружая их целиком.
2. Бесконечные последовательности
Такое нельзя сделать обычным списком — но генератор легко выдаёт «бесконечный» поток.
3. Пайплайны обработки
Генераторы удобно «цеплять» друг за другом:
Каждый шаг обрабатывает данные лениво, без лишних промежуточных списков.
---
### Итог
Итераторы дают вам каркас, генераторы — быстрый инструмент.
Если нужно точное поведение и сложное состояние — пишите свой класс-итератор.
Если хотите простой, читаемый и ленивый поток данных — используйте генераторы.
Понимание этих двух механизмов превращает
Если вы поняли, как работает
for в Python, вы уже на полпути к итераторам и генераторам. Осталось узнать, как сделать свою «ленту данных», которую можно перебирать по одному элементу — лениво, экономно и красиво.---
### Итераторы: ручной режим
Итератор — это объект, у которого есть два метода:
__iter__() и __next__(). for под капотом просто вызывает их.Создадим свой итератор, который выдает квадраты чисел от 1 до
n:class Squares:
def __init__(self, n):
self.n = n
self.current = 1
def __iter__(self):
return self
def __next__(self):
if self.current > self.n:
raise StopIteration
value = self.current ** 2
self.current += 1
return value
for num in Squares(5):
print(num)
Плюсы: полный контроль — можно кешировать, логировать, считать статистику.
Минусы: много шаблонного кода.
---
### Генераторы: автоматический режим
Генератор — это «упрощенный итератор». Достаточно функции с
yield, и Python сам создаст нужные методы.def squares(n):
current = 1
while current <= n:
yield current ** 2
current += 1
for num in squares(5):
print(num)
Код компактнее, логика та же. Функция
squares не возвращает сразу список, а выдает значения по одному — это экономит память.---
### Где это полезно на практике?
1. Работа с большими данными
def read_lines(path):
with open(path, 'r', encoding='utf-8') as f:
for line in f:
yield line.rstrip('\n')
Можно обрабатывать гигантские файлы построчно, не загружая их целиком.
2. Бесконечные последовательности
def infinite_counter(start=0, step=1):
value = start
while True:
yield value
value += step
Такое нельзя сделать обычным списком — но генератор легко выдаёт «бесконечный» поток.
3. Пайплайны обработки
Генераторы удобно «цеплять» друг за другом:
def even_numbers(numbers):
for n in numbers:
if n % 2 == 0:
yield n
def squared(numbers):
for n in numbers:
yield n * n
data = range(10)
for num in squared(even_numbers(data)):
print(num)
Каждый шаг обрабатывает данные лениво, без лишних промежуточных списков.
---
### Итог
Итераторы дают вам каркас, генераторы — быстрый инструмент.
Если нужно точное поведение и сложное состояние — пишите свой класс-итератор.
Если хотите простой, читаемый и ленивый поток данных — используйте генераторы.
Понимание этих двух механизмов превращает
for из «магии» в управляемый инструмент, с которым легко строить эффективные и элегантные конструкции в Python.👍3
Как использовать модуль
Модуль
---
### Комбинаторика без боли:
---
### Цепочки и бесконечные последовательности:
---
### Группировка и фильтрация:
---
### Комбинаторика на стероидах:
Можно перебирать комбинации с повторениями — полезно для задач типа «как разменять сумму монетами».
---
itertools для сложных операций с коллекциямиМодуль
itertools — это как швейцарский нож для работы с последовательностями. Многие задачи, которые обычно решают вложенными циклами и временными списками, здесь превращаются в одну-две строки кода.---
### Комбинаторика без боли:
product, permutations, combinationsfrom itertools import product, permutations, combinations
colors = ["red", "green"]
sizes = ["S", "M", "L"]
# Декартово произведение: все сочетания цвета и размера
for item in product(colors, sizes):
print(item)
# ('red', 'S'), ('red', 'M'), ...
digits = [1, 2, 3]
# Все перестановки длиной 3
print(list(permutations(digits, 3))) # порядок важен
# Все комбинации по 2 элемента
print(list(combinations(digits, 2))) # порядок не важен
product идеально подходит для перебора параметров в тестах или конфигурациях, а permutations и combinations — для задач по перебору вариантов (пароли, маршруты, наборы).---
### Цепочки и бесконечные последовательности:
chain, count, cyclefrom itertools import chain, count, cycle, islice
a = [1, 2]
b = [3, 4]
# Склеивание нескольких коллекций
for x in chain(a, b, [5, 6]):
print(x)
# Бесконечный счетчик + ограничение через islice
for n in islice(count(10, 2), 5):
print(n) # 10, 12, 14, 16, 18
# Циклический перебор
colors = ["red", "green", "blue"]
for color in islice(cycle(colors), 7):
print(color)
chain удобен, когда не хочется создавать новый список через +. count и cycle дают бесконечные последовательности, но с ними almost всегда используют islice, чтобы не зациклиться навсегда.---
### Группировка и фильтрация:
groupby, compressfrom itertools import groupby, compress
data = "aaabbbccccdd"
for char, group in groupby(data):
print(char, len(list(group))) # символ и сколько раз подряд встретился
items = ["apple", "banana", "cherry", "date"]
selectors = [1, 0, 1, 0]
# Выбираем только те элементы, где selector == 1
print(list(compress(items, selectors))) # ['apple', 'cherry']
groupby удобно использовать для сжатия повторов или простой аналитики. compress — это фильтрация по маске из True/False (или 1/0).---
### Комбинаторика на стероидах:
combinations_with_replacementfrom itertools import combinations_with_replacement
coins = [1, 2, 5]
for combo in combinations_with_replacement(coins, 3):
print(combo)
Можно перебирать комбинации с повторениями — полезно для задач типа «как разменять сумму монетами».
---
itertools позволяет писать компактный, но мощный код для сложных операций с коллекциями, не утопая в вложенных циклах и временных списках. Чем больше вы его используете, тем чаще ловите себя на мысли: «Так, это же удобно решить одной функцией из itertools».👍2🔥1