Введение
Итак, вы сделали первые шаги и освоили джентльменский набор Python-разработчика из первой части. Теперь в вашем арсенале есть Requests и BeautifulSoup для извлечения практически любой информации из сети, Pandas для её структурирования и анализа, pyTelegramBotAPI для создания удобных интерфейсов в виде ботов и Pillow для базовой работы с графикой.
Вы научились получать и обрабатывать данные. Но что дальше? Как хранить эту информацию надежно, а не в CSV-файлах? Как поделиться результатами своей работы с другими, предоставив им удобный доступ через API? Что делать, если сайт настолько сложен, что Requests не может справиться с его динамическим контентом? И как убедиться, что весь написанный вами код не сломается после очередного изменения?
Если вы задавали себе эти вопросы, то эта статья — ваш следующий шаг. Мы переходим от скриптов "для себя" к созданию полноценных инструментов и внедрению профессиональных практик. В этой части мы разберем пять следующих ключевых библиотек, которые откроют вам двери в мир веб-разработки, работы с базами данных, продвинутой автоматизации и надежного кода.
1. FastAPI — Современный стандарт для создания API
Что это?
FastAPI — это современный, быстрый (высокопроизводительный) веб-фреймворк для создания API на Python. Если в первой части мы использовали Requests как клиент для получения данных с чужих API, то FastAPI — это инструмент для создания таких API на стороне сервера. Он позволяет вам определять "эндпоинты" (специальные URL), к которым другие программы (или вы сами) смогут обращаться для получения или отправки данных.
Почему он, а не Django или Flask?
Это справедливый вопрос, ведь Django и Flask — проверенные временем гиганты.
- Django — это фреймворк-тяжеловес, поставляемый с "батарейками в комплекте": у него есть своя ORM для работы с базами данных, панель администратора и многое другое. Он отлично подходит для больших монолитных проектов, таких как интернет-магазины или CMS. 
- Flask — его полная противоположность. Это микро-фреймворк, который дает вам абсолютный минимум для старта, позволяя самостоятельно выбирать и подключать нужные компоненты. Идеален для небольших приложений и тех, кто любит полный контроль. 
FastAPI занимает золотую середину и имеет ключевые преимущества именно для создания API:
- Высокая производительность: Благодаря асинхронной архитектуре на базе Starlette, FastAPI является одним из самых быстрых фреймворков для Python, сопоставимым по скорости с NodeJS и Go. Это критически важно для сервисов, которые должны обрабатывать тысячи запросов в секунду. 
- Встроенная валидация данных: FastAPI использует библиотеку Pydantic, которая автоматически проверяет (валидирует) все входящие и исходящие данные на основе стандартных Python-аннотаций типов. Если клиент прислал строку вместо числа — FastAPI автоматически вернет ошибку с понятным описанием. 
- Автоматическая документация: Это главная "суперсила" FastAPI. Он автоматически генерирует интерактивную документацию для вашего API (Swagger UI и ReDoc). Вам больше не нужно вручную описывать каждый эндпоинт — просто откройте специальную страницу в браузере ( - /docs), и вы увидите полный список методов, их параметры и сможете сразу же их протестировать.
Эти особенности делают FastAPI идеальным выбором для создания микросервисов, бэкенда для современных веб- и мобильных приложений, а также для любых задач, где скорость и надежность обмена данными стоят на первом месте.
Как с ним работать: Ключевые операции
1. Установка
Для работы FastAPI нужен ASGI-сервер. Самый популярный из них — Uvicorn. Рекомендуется устанавливать их вместе с дополнительными стандартными зависимостями:
pip install "fastapi[standard]"
2. "Hello, World" в мире API
Создайте файл main.py со следующим содержимым. Это минимальное рабочее приложение:
from fastapi import FastAPI
# Создаем экземпляр приложения
app = FastAPI()
# Определяем эндпоинт для GET-запроса по корневому URL ("/")
@app.get("/")
async def read_root():
    return {"message": "Hello, World"}
Теперь запустите сервер из терминала, находясь в той же директории:
uvicorn main:app --reload
Флаг --reload автоматически перезапускает сервер при каждом изменении в коде, что очень удобно при разработке.
Откройте в браузере http://127.0.0.1:8000, и вы увидите JSON-ответ: {"message":"Hello, World"}.
3. Обработка путей и HTTP-методов
Вы можете легко создавать разные эндпоинты. Давайте добавим получение элемента по его ID (GET) и создание нового элемента (POST).
from fastapi import FastAPI
app = FastAPI()
# Эндпоинт для получения данных
@app.get("/items/{item_id}")
def read_item(item_id: int):
    return {"item_id": item_id, "name": f"Item {item_id}"}
# Эндпоинт для создания данных
@app.post("/items/")
def create_item(item_name: str, price: float):
    return {"message": "Item created successfully", "name": item_name, "price": price}
Обратите внимание, как FastAPI использует аннотации типов (item_id: int). Если вы попробуете перейти по адресу /items/abc, FastAPI автоматически вернет ошибку 422, потому что "abc" не является целым числом.
4. Валидация данных с Pydantic
Для более сложных данных, передаваемых, например, в теле POST-запроса, используются модели Pydantic. Это классы, которые описывают структуру ваших данных.
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
    name: str
    description: str | None = None  # Необязательное поле
    price: float
    is_offer: bool = False          # Поле со значением по умолчанию
app = FastAPI()
@app.post("/items/")
def create_item(item: Item):
    # Внутри функции item - это уже объект Pydantic,
    # с которым удобно работать
    return {"message": "Item received", "item_name": item.name}
Теперь FastAPI будет ожидать JSON-объект определенной структуры в теле POST-запроса. Если поле price будет строкой или поле name будет отсутствовать, клиент получит подробную ошибку.
5. Автоматическая документация
Не меняя ничего в коде, который вы уже написали, откройте в браузере http://127.0.0.1:8000/docs.
Вы увидите интерактивный интерфейс Swagger UI. FastAPI проанализировал ваш код, включая модели Pydantic, и создал полноценную документацию. Здесь вы можете:
- Увидеть все доступные эндпоинты ( - /,- /items/{item_id},- /items/).
- Развернуть каждый из них, чтобы посмотреть ожидаемые параметры и форматы данных. 
- Нажать на кнопку "Try it out", ввести тестовые данные и отправить реальный запрос на свой работающий сервер прямо из браузера. 
Также доступна альтернативная документация в формате ReDoc по адресу http://127.0.0.1:8000/redoc.
2. SQLAlchemy — Мощь баз данных без SQL-запросов
Что это?
SQLAlchemy — это ORM (Object-Relational Mapper), или объектно-реляционный преобразователь. Говоря проще, это мощная библиотека, которая позволяет вашему Python-коду взаимодействовать с реляционными базами данных (такими как PostgreSQL, MySQL, SQLite) как с обычными Python-объектами.
Вместо того чтобы писать SQL-запросы в виде текста ("SELECT * FROM users WHERE age > 30"), вы оперируете классами и методами. SQLAlchemy берет на себя задачу "перевода" ваших Python-команд на язык SQL, отправляет их в базу данных и преобразует результат обратно в удобные для вас объекты.
Зачем это нужно?
Работа с "сырыми" SQL-запросами в коде чревата несколькими проблемами, которые элегантно решает ORM:
- Безопасность: ORM по умолчанию защищает вас от SQL-инъекций — одного из самых распространенных и опасных типов атак. Библиотека сама экранирует все передаваемые данные, не позволяя злоумышленнику внедрить вредоносный код. 
- Независимость от СУБД: Вы пишете код на Python, а SQLAlchemy адаптирует его под "диалект" конкретной базы данных. Сегодня вы можете использовать простую файловую базу SQLite для разработки, а завтра, практически не меняя код, переключиться на мощный PostgreSQL для продакшена. 
- Чистота и поддержка кода: Ваш код становится более "питоничным" и объектно-ориентированным. Вместо смешения двух языков (Python и SQL) в одном файле, вы работаете в единой парадигме. Это упрощает чтение, отладку и дальнейшее развитие проекта. 
- Удобство работы: Сложные - JOIN-запросы и выборки данных становятся методами, которые можно объединять в цепочки, что гораздо нагляднее многострочных SQL-конструкций.
Как с ней работать: Ключевые операции
Давайте создадим простую базу данных для хранения информации о пользователях. Для примера мы будем использовать SQLite, так как она не требует установки отдельного сервера и хранит все данные в одном файле.
1. Установка
pip install sqlalchemy
2. Определение модели (описание таблицы)
Модель — это Python-класс, который описывает структуру таблицы в базе данных. Каждый экземпляр этого класса будет соответствовать одной строке в таблице.
# main_db.py
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import DeclarativeBase, sessionmaker
# 1. Определение базового класса для моделей
class Base(DeclarativeBase):
    pass
# 2. Описание нашей таблицы в виде класса
class User(Base):
    __tablename__ = 'users'  # Имя таблицы в базе данных
    id = Column(Integer, primary_key=True)
    name = Column(String)
    age = Column(Integer)
    # Метод для красивого отображения объекта при печати
    def __repr__(self):
        return f"<User(id={self.id}, name='{self.name}', age={self.age})>"
3. Создание подключения (Engine) и таблиц
Engine — это "сердце" SQLAlchemy, управляющее подключением к базе данных.
# main_db.py (продолжение)
# 3. Настройка подключения к базе данных
# "sqlite:///users.db" означает, что мы используем SQLite,
# а база данных будет храниться в файле users.db
engine = create_engine("sqlite:///users.db")
# 4. Создание таблицы в базе данных
# Эта команда проверит, существует ли таблица 'users', и если нет — создаст её
Base.metadata.create_all(engine)
После выполнения этого кода в вашей папке появится файл users.db.
4. Создание сессии и добавление данных (INSERT)
Сессия — это временное "окно" для работы с базой данных. Все операции (добавление, изменение, удаление) сначала регистрируются в сессии, а затем одной командой (commit) сохраняются в базу.
# main_db.py (продолжение)
# 5. Создание фабрики сессий
Session = sessionmaker(bind=engine)
# Открываем сессию для работы
session = Session()
# Создаем объекты (строки для таблицы)
user1 = User(name='Анна', age=28)
user2 = User(name='Борис', age=34)
user3 = User(name='Виктор', age=29)
# Добавляем объекты в сессию
session.add_all([user1, user2, user3])
# Сохраняем все изменения в базу данных
session.commit()
# Закрываем сессию
session.close()
5. Чтение и фильтрация данных (SELECT)
Теперь, когда данные сохранены, давайте их получим.
# main_db.py (продолжение)
# Снова открываем сессию для нового запроса
session = Session()
# 1. Получить всех пользователей
all_users = session.query(User).all()
print("Все пользователи:", all_users)
# 2. Получить пользователей старше 30
adults = session.query(User).filter(User.age > 30).all()
print("Пользователи старше 30:", adults)
# 3. Получить конкретного пользователя по имени
boris = session.query(User).filter_by(name='Борис').first()
# .first() возвращает первый найденный результат или None
print("Найден Борис:", boris)
# Не забываем закрыть сессию
session.close()
Результат выполнения:
Все пользователи: [<User(id=1, name='Анна', age=28)>, <User(id=2, name='Борис', age=34)>, <User(id=3, name='Виктор', age=29)>]
Пользователи старше 30: [<User(id=2, name='Борис', age=34)>]
Найден Борис: <User(id=2, name='Борис', age=34)>
Как видите, SQLAlchemy позволяет работать с базой данных в чисто объектном стиле, полностью абстрагируясь от написания SQL-кода. Это делает разработку быстрее, безопаснее и гораздо приятнее.
3. Selenium — Автоматизация действий в браузере
Что это?
Selenium WebDriver — это библиотека для автоматизации браузеров. В отличие от Requests, который просто скачивает HTML-код страницы, Selenium запускает и управляет настоящим, полноценным браузером (например, Chrome или Firefox). Ваш Python-скрипт получает возможность программно выполнять все действия, которые мог бы сделать человек: кликать по кнопкам, вводить текст в формы, прокручивать страницу и даже выполнять JavaScript-код.
Основная задача Selenium — не парсинг, а взаимодействие. Он нужен, чтобы "довести" веб-страницу до нужного состояния, а уже потом извлечь из неё финальный HTML для дальнейшей обработки.
Когда BeautifulSoup недостаточно?
Связка requests + BeautifulSoup из первой части идеально подходит для статичных сайтов. То есть для страниц, где весь контент доступен сразу после загрузки в исходном HTML-коде. Но современные веб-приложения устроены гораздо сложнее. Вы столкнетесь с проблемами, если:
- Контент подгружается динамически: Многие сайты сначала загружают "каркас" страницы, а затем с помощью JavaScript подгружают основной контент. - Requestsполучит только этот пустой каркас.
- Нужно взаимодействие: Данные появляются только после определенных действий — например, нужно нажать на кнопку "Показать еще", выбрать опцию в выпадающем списке или прокрутить страницу вниз (бесконечная прокрутка). 
- Требуется авторизация: Чтобы получить доступ к данным, нужно сначала залогиниться, заполнив форму. 
- Сайт активно использует JavaScript для защиты от ботов, и простые запросы блокируются. 
Во всех этих случаях на помощь приходит Selenium. Он имитирует поведение реального пользователя в реальном браузере, позволяя дождаться загрузки всех скриптов и получить доступ к контенту, который недоступен для requests.
Как с ним работать: Ключевые операции
1. Установка
Для работы Selenium нужны две вещи: сама библиотека и специальный драйвер для вашего браузера. Проще всего управлять драйверами с помощью дополнительной утилиты.
pip install selenium webdriver-manager
webdriver-manager будет автоматически скачивать и подготавливать нужную версию драйвера для вашего Chrome или Firefox.
2. Запуск браузера и открытие страницы
Это базовый скрипт, который откроет браузер Chrome, перейдет на сайт и затем закроет его.
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
# Автоматически скачиваем и устанавливаем драйвер для Chrome
service = Service(ChromeDriverManager().install())
# Создаем экземпляр драйвера
driver = webdriver.Chrome(service=service)
# Открываем страницу
driver.get("https://www.python.org")
# Выводим заголовок страницы, чтобы убедиться, что все работает
print(driver.title)
# Закрываем браузер
driver.quit()
3. Поиск элементов
Чтобы взаимодействовать со страницей, нужно сначала найти на ней нужные элементы (поле ввода, кнопку и т.д.). Для этого используется метод find_element() и специальный объект By.
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
# ... (код для инициализации драйвера)
driver.get("https://www.python.org")
# Находим поле поиска по его имени атрибута 'name'
search_bar = driver.find_element(By.NAME, "q")
Самые популярные способы поиска (By):
- By.ID: по уникальному идентификатору- id.
- By.NAME: по атрибуту- name(часто у полей формы).
- By.CLASS_NAME: по имени CSS-класса.
- By.CSS_SELECTOR: по CSS-селектору (очень мощный способ).
- By.XPATH: по XPath-выражению (самый гибкий, но и самый сложный способ).
- By.TAG_NAME: по имени тега (например,- 'h1').
4. Взаимодействие с элементами
После того как элемент найден, с ним можно выполнять действия.
# ... (код, где мы нашли search_bar)
# Очищаем поле ввода на случай, если там уже есть текст
search_bar.clear()
# Вводим текст
search_bar.send_keys("getting started with python")
# Нажимаем Enter
search_bar.send_keys(Keys.RETURN)
5. Ожидание загрузки контента
Это критически важный момент. После клика или отправки формы страница не обновляется мгновенно. Если попытаться найти элемент на новой странице сразу, скрипт выдаст ошибку, так как элемент еще не успел появиться.
Плохой способ — использовать time.sleep(5). Это ненадежно и неэффективно.
 Правильный способ — использовать явные ожидания (Explicit Waits). Мы говорим Selenium ждать до тех пор, пока не выполнится определенное условие (например, пока нужный элемент не станет видимым).
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# ... (после отправки формы поиска)
try:
    # Ждем до 10 секунд, пока не появится элемент с id='content'
    # Это гарантирует, что страница результатов поиска загрузилась
    element = WebDriverWait(driver, 10).until(
        EC.presence_of_element_located((By.ID, "content"))
    )
    print("Страница результатов загружена!")
except Exception as e:
    print("Не дождались загрузки страницы результатов.")
    driver.quit()
6. Извлечение данных и передача в BeautifulSoup
После всех манипуляций и ожиданий у нас есть страница в нужном состоянии. Теперь можно забрать её HTML-код и передать его нашему старому знакомому BeautifulSoup для удобного парсинга.
from bs4 import BeautifulSoup
# ... (после того как дождались загрузки результатов)
# Получаем HTML-код страницы, который сейчас отображается в браузере
html_code = driver.page_source
# Создаем объект BeautifulSoup
soup = BeautifulSoup(html_code, 'lxml')
# Ищем все заголовки результатов поиска
# (CSS-селектор может отличаться, это просто пример)
search_results = soup.find_all('h3')
print("\nНайденные результаты:")
for result in search_results:
    print(result.text.strip())
# Не забываем закрыть браузер в конце
driver.quit()
Таким образом, Selenium и BeautifulSoup не конкурируют, а дополняют друг друга. Selenium выполняет "грязную" работу по автоматизации браузера, а BeautifulSoup предоставляет удобный API для разбора уже полученного HTML.
4. Asyncio — Ускоряем код через асинхронность
Что это?
asyncio — это встроенная в Python библиотека для написания конкурентного (concurrent) кода с использованием синтаксиса async/await. Она позволяет вашему приложению выполнять множество задач, связанных с ожиданием (I/O-bound tasks), практически одновременно, не блокируя при этом основной поток выполнения.
Ключевое слово здесь — ожидание. Большинство программ тратят огромное количество времени не на вычисления, а на ожидание ответа от сети, базы данных или жесткого диска. asyncio позволяет использовать это время ожидания с пользой.
Проблема синхронного кода
Представьте себе шеф-повара, который готовит два блюда: суп и стейк.
- Синхронный повар: Он ставит воду для супа на плиту и стоит, уставившись на кастрюлю, 10 минут, пока она не закипит. Только после этого он достает мясо для стейка и начинает его готовить. Весь процесс занимает суммарное время: 10 минут (кипячение) + 5 минут (готовка стейка) = 15 минут. 
- Асинхронный повар: Он ставит воду для супа на плиту, и пока она закипает (задача в режиме ожидания), он тут же переключается на подготовку стейка. Когда вода закипает, он на секунду отвлекается от стейка, чтобы закинуть ингредиенты в суп, и возвращается к стейку. Весь процесс занимает время самой долгой операции: ~10 минут. 
То же самое происходит в коде. Если вам нужно сделать 100 запросов к разным API, и каждый занимает время, синхронный код на requests будет выполнять их один за другим. asyncio отправит все 100 запросов почти одновременно и будет ждать их завершения, уложившись во время, сопоставимое со временем самого долгого запроса.
Как с ним работать: Ключевые операции
Для демонстрации нам понадобится HTTP-клиент, который "умеет" работать асинхронно. requests для этого не подходит, поэтому мы будем использовать его популярный асинхронный аналог — aiohttp.
1. Установка
asyncio — часть стандартной библиотеки, но aiohttp нужно установить.
pip install aiohttp
2. Основы синтаксиса: async def и await
- async def: Когда вы объявляете функцию таким образом, вы создаете корутину (coroutine). Это "особая" функция, выполнение которой можно приостановить и возобновить.
- await: Это ключевое слово говорит Python: "Выполнение этой операции (например, сетевого запроса) займет время. Пока мы ждем, ты можешь приостановить эту корутину и пойти поработать над другими задачами". Использовать- awaitможно только внутри- async defфункции.
- asyncio.run(): Это главная команда, которая запускает так называемый цикл событий (event loop) и выполняет в нём вашу основную асинхронную функцию.
3. Пример с сетевыми запросами
Давайте напишем асинхронную программу, которая конкурентно скачает несколько веб-страниц.
import asyncio
import aiohttp
import time
async def fetch_url(session, url):
    """Асинхронно скачивает один URL и возвращает его заголовок."""
    print(f"Начинаю загрузку {url}")
    try:
        async with session.get(url) as response:
            # Читаем содержимое, чтобы запрос был полностью выполнен
            content = await response.text()
            print(f"Завершил загрузку {url}, статус: {response.status}")
            return url, response.status
    except Exception as e:
        print(f"Ошибка при запросе к {url}: {e}")
        return url, None
# Это наша главная асинхронная функция
async def main():
    urls = [
        "https://example.com",
        "https://www.python.org",
        "https://www.google.com",
    ]
    
    start_time = time.perf_counter()
    async with aiohttp.ClientSession() as session:
        # Создаем список задач для конкурентного выполнения
        tasks = [fetch_url(session, url) for url in urls]
        # Запускаем все задачи и ждем их завершения
        results = await asyncio.gather(*tasks)
    
    end_time = time.perf_counter()
    print("\n--- Результаты ---")
    for url, status in results:
        print(f"URL: {url}, Статус: {status}")
        
    print(f"\nОбщее время выполнения: {end_time - start_time:.2f} секунд")
# Запускаем выполнение асинхронной программы
if __name__ == "__main__":
    asyncio.run(main())
4. Параллельный запуск с asyncio.gather
Ключевая магия происходит здесь:
tasks = [fetch_url(session, url) for url in urls]
results = await asyncio.gather(*tasks)
Мы не вызываем await на каждой задаче по очереди. Вместо этого мы создаем список всех наших "задач" (корутин) и передаем их в asyncio.gather. Эта функция запускает их всех конкурентно. Когда одна задача ждет ответа от сети, asyncio переключается на другую. В итоге gather дожидается, пока все задачи не будут выполнены.
Примерный результат выполнения кода:
Начинаю загрузку https://example.com
Начинаю загрузку https://www.python.org
Начинаю загрузку https://www.google.com
Завершил загрузку https://example.com, статус: 200
Завершил загрузку https://www.google.com, статус: 200
Завершил загрузку https://www.python.org, статус: 200
--- Результаты ---
URL: https://example.com, Статус: 200
URL: https://www.python.org, Статус: 200
URL: https://www.google.com, Статус: 200
Общее время выполнения: 0.35 секунд
Вывод:
 Обратите внимание на порядок вывода сообщений. Все загрузки начались практически одновременно, а завершились по мере получения ответа от серверов. asyncio не ждал, пока example.com загрузится, чтобы начать загрузку python.org.
Хотя на быстрых сайтах выигрыш в абсолютных числах (доли секунды) может показаться небольшим, представьте, что вам нужно обработать тысячи таких запросов. Разница между синхронным (последовательным) и асинхронным (конкурентным) подходом станет колоссальной. asyncio — это не серебряная пуля, но для задач с большим количеством сетевых или дисковых операций (веб-парсеры, API-шлюзы, боты) это стандарт для достижения высокой производительности.
5. Pytest — Делаем код надежным
Что это?
Pytest — это фреймворк для тестирования Python-кода. Он позволяет писать тесты — небольшие, изолированные фрагменты кода, которые проверяют, что ваши функции и классы работают именно так, как вы ожидаете. Pytest берет на себя всю рутину по поиску и запуску этих тестов и предоставляет подробные отчеты об ошибках, если что-то пошло не так.
Хотя в Python есть встроенный модуль unittest, Pytest стал де-факто стандартом в индустрии благодаря своему простому синтаксису, мощным возможностям и огромной экосистеме плагинов. Его философия — тесты должны быть легкими для написания и чтения.
Зачем писать тесты?
Написание тестов поначалу может показаться дополнительной и скучной работой. Но это одна из самых важных инвестиций в ваш проект, которая окупается многократно. Тесты — это ваша "система безопасности".
- Уверенность в изменениях (рефакторинге): Вы решили улучшить свою функцию, сделав ее более быстрой или читаемой. Как убедиться, что вы ничего не сломали? Просто запустите тесты. Если они все еще проходят — вы можете быть уверены, что старая функциональность работает как прежде. 
- Защита от регрессий: "Регрессия" — это когда новая функция или исправление ошибки ломает что-то, что работало раньше. Автоматические тесты — лучший способ ловить такие проблемы на ранней стадии. 
- Живая документация: Тесты — это отличные примеры использования вашего кода. Глядя на тест, новый разработчик в команде может сразу понять, какие аргументы принимает функция и какой результат она должна возвращать. 
- Улучшение дизайна кода: Когда вы пишете код с мыслью о том, как его протестировать, вы инстинктивно создаете более простые, модульные и независимые функции, которые легче поддерживать. 
Как с ним работать: Ключевые операции
1. Установка
pip install pytest
2. Написание простого теста
Допустим, у нас есть файл my_functions.py с простой функцией:
# my_functions.py
def add(x, y):
    """Складывает два числа."""
    return x + y
Теперь создадим файл для тестов. По соглашению, Pytest автоматически находит тесты в файлах с именами test_*.py или *_test.py.
Создадим файл test_my_functions.py:
# test_my_functions.py
from my_functions import add
# Название тестовой функции должно начинаться с `test_`
def test_add_positive_numbers():
    # `assert` - это стандартная инструкция Python.
    # Она проверяет, является ли выражение истинным.
    # Если нет — тест провалится.
    assert add(2, 3) == 5
def test_add_negative_numbers():
    assert add(-1, -1) == -2
def test_add_mixed_numbers():
    assert add(-5, 5) == 0
3. Структура и запуск
Теперь, когда у вас есть эти два файла в одной папке:
project/
├── my_functions.py
└── test_my_functions.py
Просто откройте терминал в этой папке и выполните команду:
pytest
Pytest автоматически найдет и запустит все тесты. Вы увидите примерно такой вывод:
========================= test session starts ==========================
...
collected 3 items
test_my_functions.py ...                                         [100%]
========================== 3 passed in 0.01s ===========================
Три точки (...) означают, что все три теста успешно пройдены.
А что, если тест провалится? Давайте намеренно сломаем один тест:
# test_my_functions.py
def test_add_positive_numbers_fails_intentionally():
    assert add(2, 3) == 6  # Ошибка! 2 + 3 это 5, а не 6.
Запускаем pytest снова:
========================= test session starts ==========================
...
collected 1 item
test_my_functions.py F                                           [100%]
=============================== FAILURES ===============================
__ test_add_positive_numbers_fails_intentionally ___
    def test_add_positive_numbers_fails_intentionally():
>       assert add(2, 3) == 6
E       assert 5 == 6
test_my_functions.py:5: AssertionError
======================= 1 failed in 0.03s ========================
Pytest не просто сообщает об ошибке (F), он показывает, на какой строке она произошла, и даже выводит значения, которые привели к провалу (assert 5 == 6). Это невероятно ускоряет поиск и исправление багов.
4. Фикстуры — подготовка окружения для тестов
Фикстуры — одна из самых мощных возможностей Pytest. Это функции, которые "готовят сцену" для ваших тестов: создают объекты, подключаются к тестовой базе данных, создают временные файлы и т.д.
Представьте, что у вас много тестов, которым нужен один и тот же набор данных. Вместо того чтобы создавать его в каждом тесте, можно сделать фикстуру.
# test_my_functions.py
import pytest
@pytest.fixture
def sample_list():
    """Эта фикстура предоставляет простой список для тестов."""
    return [1, 2, 3, 4, 5]
# Pytest автоматически найдет фикстуру `sample_list`
# и передаст ее результат в качестве аргумента.
def test_sum_of_list(sample_list):
    assert sum(sample_list) == 15
def test_length_of_list(sample_list):
    assert len(sample_list) == 5
Использование pytest превращает тестирование из рутины в мощный инструмент разработки, который дает уверенность в качестве вашего кода и позволяет смело вносить изменения в проект.
Заключение
Итак, мы сделали обзор пяти мощных инструментов, которые превращают знание синтаксиса Python в реальный навык решения прикладных задач.
Надеюсь, эти краткие примеры послужили для вас хорошей отправной точкой. Лучший способ закрепить материал — это практика и обсуждение. Делитесь своим опытом и мнениями в комментариях: какие библиотеки вы считаете обязательными для старта? Для тех, кому интересен более живой формат обмена идеями и регулярные разборы небольших практических задач, я также веду авторский канал в Telegram, где продолжается обсуждение подобных тем.
 
           
 
TeaDove
Asyncio корректнее сравнивать с тредами, а не синхронностью
В питоне requests спокойно может работать в 10 потоков (паралельно) через concurrency.threads.ThreadPoolExecutor, без необходимости использования asyncio
Фишка asyncio в том, что он использует корутины, а они дешевле тредов, поэтому лучше использовать их
RomanVelichkin
Asyncio - single-threaded (однопоточный), поэтому он всяко дешевле, но он и не обладает способностью к параллелизму.
enamored_poc Автор
Питон в целом не обладает способность к параллелизму.