Как настроить асинхронные 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
Понимание и применение генераторов: экономия памяти с
Многие новички в Python сначала пишут всё через списки. Это удобно, но не всегда безопасно для памяти. Представьте файл на 10 миллионов строк: загружать его целиком в список — плохая идея. Вот тут и вступают в игру генераторы и ключевое слово
---
### Что такое генератор?
Генератор — это «ленивый» источник данных. Он не хранит все значения сразу, а выдает их по одному по мере запроса. Вместо
Здесь не создается список
---
### Генераторы против списков: почему это экономично
Используйте генератор, когда:
- данные большие или потенциально бесконечные;
- вы проходите по последовательности один раз;
- вам не нужно случайно обращаться к элементу по индексу.
---
### Практический пример: чтение большого файла
Мы не загружаем весь файл в память, а читаем его построчно. Для логов, дампов и CSV на гигабайты — это буквально спасение.
---
### Комбинирование генераторов
Генераторы отлично «цепляются» друг за друга:
Каждый шаг выдает только один элемент: минимум памяти, максимум гибкости.
---
### Главное запомнить
-
- Генераторы не хранят все данные, а создают их «по требованию».
- Это критично для работы с большими файлами, потоками данных и длинными диапазонами.
Как только ловите себя на том, что создаете огромный список «просто чтобы по нему один раз пройтись» — подумайте: а не пора ли заменить его на генератор?
yieldМногие новички в Python сначала пишут всё через списки. Это удобно, но не всегда безопасно для памяти. Представьте файл на 10 миллионов строк: загружать его целиком в список — плохая идея. Вот тут и вступают в игру генераторы и ключевое слово
yield.---
### Что такое генератор?
Генератор — это «ленивый» источник данных. Он не хранит все значения сразу, а выдает их по одному по мере запроса. Вместо
return в функции используется yield.def count_up_to(n):
current = 1
while current <= n:
yield current
current += 1
gen = count_up_to(5)
for num in gen:
print(num)
Здесь не создается список
[1, 2, 3, 4, 5]. В каждый момент в памяти только одно число.---
### Генераторы против списков: почему это экономично
# список
squares_list = [i * i for i in range(10_000_000)]
# генератор
squares_gen = (i * i for i in range(10_000_000))
squares_list реально занимает память под 10 млн чисел. squares_gen — это объект, который умеет по запросу выдавать квадрат очередного числа. Его размер в памяти — примерно как у обычного небольшого объекта, независимо от диапазона.Используйте генератор, когда:
- данные большие или потенциально бесконечные;
- вы проходите по последовательности один раз;
- вам не нужно случайно обращаться к элементу по индексу.
---
### Практический пример: чтение большого файла
def read_large_file(path):
with open(path, 'r', encoding='utf-8') as f:
for line in f:
yield line.strip()
for line in read_large_file('logs.txt'):
if 'ERROR' in line:
print(line)
Мы не загружаем весь файл в память, а читаем его построчно. Для логов, дампов и CSV на гигабайты — это буквально спасение.
---
### Комбинирование генераторов
Генераторы отлично «цепляются» друг за друга:
def even_numbers(numbers):
for n in numbers:
if n % 2 == 0:
yield n
nums = (i for i in range(100)) # генераторное выражение
evens = even_numbers(nums) # генератор-фильтр
for n in evens:
print(n)
Каждый шаг выдает только один элемент: минимум памяти, максимум гибкости.
---
### Главное запомнить
-
yield превращает функцию в генератор.- Генераторы не хранят все данные, а создают их «по требованию».
- Это критично для работы с большими файлами, потоками данных и длинными диапазонами.
Как только ловите себя на том, что создаете огромный список «просто чтобы по нему один раз пройтись» — подумайте: а не пора ли заменить его на генератор?
👍2🔥2❤1
Работа с коллекциями: полезные структуры данных из
Стандартный список и словарь решают 80% задач. Модуль
---
### 1.
Удобен для анализа логов, текста, статистики кликов и т.д.
---
### 2.
Обычный словарь выбросит ошибку, если обратиться к несуществующему ключу.
Отлично подходит для группировки данных.
---
### 3.
Используется для очередей, буферов, слайдинговых окон.
---
### 4.
Обычный кортеж — это
Легковесная альтернатива классам: почти не занимает памяти, но делает код понятнее.
---
Модуль
collectionsСтандартный список и словарь решают 80% задач. Модуль
collections помогает закрыть остальные 20% — красиво, быстро и без велосипеда. Разберём самые полезные структуры: Counter, defaultdict, deque и namedtuple.---
### 1.
Counter: кто здесь самый частый?Counter считает, сколько раз элемент встретился в последовательности.from collections import Counter
text = "banana apple banana orange apple banana"
words = text.split()
freq = Counter(words)
print(freq) # Counter({'banana': 3, 'apple': 2, 'orange': 1})
print(freq.most_common(1)) # [('banana', 3)]
Удобен для анализа логов, текста, статистики кликов и т.д.
---
### 2.
defaultdict: словарь, который не ругаетсяОбычный словарь выбросит ошибку, если обратиться к несуществующему ключу.
defaultdict сам создаст значение по умолчанию.from collections import defaultdict
scores = [("Alice", 10), ("Bob", 5), ("Alice", 7)]
user_scores = defaultdict(list)
for name, score in scores:
user_scores[name].append(score)
print(user_scores) # {'Alice': [10, 7], 'Bob': [5]}
Отлично подходит для группировки данных.
---
### 3.
deque: быстрый список с двух концовlist медленный для вставки/удаления в начале. deque (double-ended queue) делает это за O(1).from collections import deque
queue = deque()
queue.append("task_1")
queue.append("task_2")
queue.appendleft("urgent_task")
print(queue) # deque(['urgent_task', 'task_1', 'task_2'])
queue.pop() # убрали с конца
queue.popleft() # убрали с начала
Используется для очередей, буферов, слайдинговых окон.
---
### 4.
namedtuple: читаемые кортежиОбычный кортеж — это
item[0], item[1] и постоянное забывание, где что. namedtuple даёт имена полям.from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y) # 3 4
print(p[0], p[1]) # тоже работает
Легковесная альтернатива классам: почти не занимает памяти, но делает код понятнее.
---
Модуль
collections — это маленький апгрейд стандартных структур, который сильно упрощает жизнь. Освоив эти четыре инструмента, вы уже будете писать более чистый и эффективный код, чем большинство начинающих.🔥4