← Назад к вопросам

Что такое ORM и какие ORM есть для Python?

1.6 Junior🔥 171 комментариев
#Архитектура и паттерны#Базы данных (SQL)

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

ORM в Python

ORM (Object-Relational Mapping) — это технология, которая позволяет работать с реляционной базой данных, используя объекты языка программирования вместо прямого SQL. ORM преобразует строки таблиц БД в объекты Python и наоборот.

Зачем нужна ORM

# Без ORM: прямой SQL (сложнее)
import sqlite3

conn = sqlite3.connect("users.db")
cursor = conn.cursor()

# Создание
cursor.execute(
    "INSERT INTO users (name, email) VALUES (?, ?)",
    ("Иван", "ivan@example.com")
)
conn.commit()

# Чтение
cursor.execute("SELECT * FROM users WHERE id = ?", (1,))
row = cursor.fetchone()
user = {"id": row[0], "name": row[1], "email": row[2]}

# Обновление
cursor.execute(
    "UPDATE users SET email = ? WHERE id = ?",
    ("newemail@example.com", 1)
)
conn.commit()

# Удаление
cursor.execute("DELETE FROM users WHERE id = ?", (1,))
conn.commit()

conn.close()

# С ORM: объектно-ориентированный подход (проще)
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import declarative_base, Session

Base = declarative_base()

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    name = Column(String)
    email = Column(String)

engine = create_engine("sqlite:///users.db")
session = Session(engine)

# Создание
user = User(name="Иван", email="ivan@example.com")
session.add(user)
session.commit()

# Чтение
user = session.query(User).filter(User.id == 1).first()

# Обновление
user.email = "newemail@example.com"
session.commit()

# Удаление
session.delete(user)
session.commit()

Преимущества ORM

✓ Работаешь с объектами, а не SQL строками
✓ Безопасность от SQL инъекций (автоматическая параметризация)
✓ Код независим от конкретной БД (SQLite, PostgreSQL, MySQL)
✓ Меньше кода для CRUD операций
✓ Встроенные отношения (relations) между объектами
✓ Валидация на уровне модели
✓ Миграции (в некоторых ORM)

Недостатки ORM

✗ Сложные запросы писать сложнее
✗ Производительность хуже, чем сырой SQL
✗ Можно случайно создать N+1 запросов
✗ Кривая обучения
✗ Некоторые ORM "магические" и сложны в отладке

ORM для Python

1. SQLAlchemy (самая популярная)

Мощная, гибкая, используется везде. Есть два стиля: ORM и Core.

from sqlalchemy import create_engine, Column, Integer, String, ForeignKey, DateTime
from sqlalchemy.orm import declarative_base, Session, relationship
from datetime import datetime

Base = declarative_base()

class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    name = Column(String(50), nullable=False)
    email = Column(String(100), unique=True)
    created_at = Column(DateTime, default=datetime.utcnow)
    
    posts = relationship("Post", back_populates="author")

class Post(Base):
    __tablename__ = "posts"
    id = Column(Integer, primary_key=True)
    title = Column(String, nullable=False)
    user_id = Column(Integer, ForeignKey("users.id"))
    
    author = relationship("User", back_populates="posts")

engine = create_engine("postgresql://user:password@localhost/mydb")
Base.metadata.create_all(engine)

with Session(engine) as session:
    # Создание с отношением
    user = User(name="Иван", email="ivan@example.com")
    post = Post(title="Моя первая статья", author=user)
    
    session.add(user)
    session.commit()
    
    # Чтение с eager loading
    user = session.query(User).filter(User.name == "Иван").first()
    print(user.posts)  # Автоматически загружает посты

2. Django ORM

Проста в использовании, встроена в Django, но привязана к фреймворку.

# models.py
from django.db import models

class User(models.Model):
    name = models.CharField(max_length=100)
    email = models.EmailField(unique=True)
    created_at = models.DateTimeField(auto_now_add=True)
    
    def __str__(self):
        return self.name

class Post(models.Model):
    title = models.CharField(max_length=200)
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="posts")

# Использование
user = User.objects.create(name="Иван", email="ivan@example.com")

post = Post.objects.create(title="Статья", author=user)

user = User.objects.get(name="Иван")
user.posts.all()  # Все посты пользователя

user.email = "new@example.com"
user.save()

user.delete()

3. Peewee

Лёгкая и простая ORM, хорошая для небольших проектов.

from peewee import SqliteDatabase, Model, CharField, ForeignKeyField

db = SqliteDatabase("my_app.db")

class BaseModel(Model):
    class Meta:
        database = db

class User(BaseModel):
    name = CharField()
    email = CharField(unique=True)

class Post(BaseModel):
    title = CharField()
    author = ForeignKeyField(User, backref="posts")

db.create_tables([User, Post])

# Использование
user = User.create(name="Иван", email="ivan@example.com")
post = Post.create(title="Статья", author=user)

for post in user.posts:
    print(post.title)

4. Tortoise ORM

Асинхронная ORM для async/await, похожа на Django ORM.

from tortoise import fields, run_async
from tortoise.models import Model
from tortoise.contrib.pydantic import pydantic_model_creator

class User(Model):
    id = fields.IntField(pk=True)
    name = fields.CharField(max_length=100)
    email = fields.CharField(max_length=100, unique=True)
    created_at = fields.DatetimeField(auto_now_add=True)
    
    posts: fields.ReverseRelation["Post"]

class Post(Model):
    id = fields.IntField(pk=True)
    title = fields.CharField(max_length=200)
    author: fields.ForeignKeyRelation[User] = fields.ForeignKeyField(
        "models.User", related_name="posts"
    )

async def main():
    # Асинхронные операции
    user = await User.create(name="Иван", email="ivan@example.com")
    post = await Post.create(title="Статья", author=user)
    
    user = await User.get(name="Иван")
    posts = await user.posts.all()
    print(posts)

run_async(main())

5. SQLModel

Комбинирует SQLAlchemy и Pydantic, современный подход.

from sqlmodel import SQLModel, Field, create_engine, Session
from typing import Optional

class User(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    email: str

engine = create_engine("sqlite:///database.db")
SQLModel.metadata.create_all(engine)

with Session(engine) as session:
    user = User(name="Иван", email="ivan@example.com")
    session.add(user)
    session.commit()
    
    user = session.query(User).filter(User.name == "Иван").first()
    print(user.dict())  # {'id': 1, 'name': 'Иван', 'email': 'ivan@example.com'}

Сравнение ORM

┌─────────────┬──────────────┬─────────────┬───────────┬───────────┐
│ ORM         │ Популярность │ Простота    │ Гибкость  │ Async     │
├─────────────┼──────────────┼─────────────┼───────────┼───────────┤
│ SQLAlchemy  │ ★★★★★       │ ★★★☆☆      │ ★★★★★    │ Да        │
│ Django ORM  │ ★★★★★       │ ★★★★★      │ ★★★☆☆    │ Нет*      │
│ Peewee      │ ★★★☆☆       │ ★★★★☆      │ ★★★☆☆    │ Нет       │
│ Tortoise    │ ★★☆☆☆       │ ★★★★☆      │ ★★★☆☆    │ Да        │
│ SQLModel    │ ★★☆☆☆       │ ★★★★★      │ ★★★★☆    │ Да        │
└─────────────┴──────────────┴─────────────┴───────────┴───────────┘
* Django ORM получит async в 5.0

Типичные проблемы ORM

N+1 Problem (N плюс 1 запросов)

# Плохо: N+1 запросов
users = session.query(User).all()  # 1 запрос
for user in users:
    print(user.posts)  # N запросов (по одному на каждого пользователя!)

# Хорошо: 2 запроса
from sqlalchemy.orm import joinedload

users = session.query(User).options(joinedload(User.posts)).all()
# 1 запрос с JOIN, посты загружены

Lazy Loading (лишний запрос в неожиданном месте)

# Плохо
user = session.query(User).filter(User.id == 1).first()
for post in user.posts:  # Если posts ещё не загружены → ещё один запрос!
    print(post.title)

# Хорошо
from sqlalchemy.orm import selectinload

user = session.query(User).options(selectinload(User.posts)).filter(
    User.id == 1
).first()
for post in user.posts:  # Уже загружены, нет доп. запроса
    print(post.title)

Когда использовать ORM vs Raw SQL

# Используй ORM для:
# - CRUD операций (Create, Read, Update, Delete)
# - Простых запросов с фильтрацией
# - Отношений между таблицами
# - Быстрой разработки

user = session.query(User).filter(User.age > 18).all()

# Используй Raw SQL для:
# - Сложных агрегаций
# - Оптимизации производительности
# - Специфичных для БД функций
# - Когда ORM создаёт неоптимальный SQL

from sqlalchemy import text

result = session.execute(
    text("""
        SELECT u.name, COUNT(p.id) as post_count
        FROM users u
        LEFT JOIN posts p ON u.id = p.user_id
        GROUP BY u.id
        HAVING COUNT(p.id) > 5
    """)
)

Резюме

  • ORM: преобразует таблицы БД в Python объекты
  • SQLAlchemy: самая мощная и популярная ORM
  • Django ORM: проста, но привязана к Django
  • Peewee: лёгкая для маленьких проектов
  • Tortoise/SQLModel: асинхронные ORM для современных приложений
  • Проблемы: N+1, lazy loading, неоптимальный SQL
  • Совет: учись писать эффективные запросы, иногда raw SQL быстрее
Что такое ORM и какие ORM есть для Python? | PrepBro