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

В чем разница между PUT и PATCH?

1.0 Junior🔥 221 комментариев
#REST API и HTTP

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

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

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

В чём разница между PUT и PATCH

PUT и PATCH — это два разных HTTP метода для обновления ресурсов на сервере. Хотя на первый взгляд они решают одну задачу, они кардинально отличаются в подходе и имеют разные последствия при использовании. Это критически важно понимать при разработке REST API.

Основная разница

PUT — полное замещение ресурса

PUT заменяет весь ресурс на переданные данные. Если не передать какое-то поле — оно будет удалено или обнулено:

import requests

# Исходный ресурс на сервере:
# {"id": 1, "name": "Иван", "email": "ivan@example.com", "phone": "+7-900-111-11-11"}

# PUT запрос с неполными данными
response = requests.put(
    "https://api.example.com/users/1",
    json={"name": "Иван", "email": "new_ivan@example.com"}
    # Обратите внимание: phone не передали!
)

# После PUT результат:
# {"id": 1, "name": "Иван", "email": "new_ivan@example.com"}  # phone исчез!

print(response.json())
# Ответ сервера: полностью новый объект без phone

PATCH — частичное обновление ресурса

PATCH обновляет только переданные поля. Остальные остаются без изменений:

import requests

# Исходный ресурс:
# {"id": 1, "name": "Иван", "email": "ivan@example.com", "phone": "+7-900-111-11-11"}

# PATCH запрос с частичными данными
response = requests.patch(
    "https://api.example.com/users/1",
    json={"email": "new_ivan@example.com"}
    # Передали только email
)

# После PATCH результат:
# {"id": 1, "name": "Иван", "email": "new_ivan@example.com", "phone": "+7-900-111-11-11"}
# phone остался на месте!

print(response.json())

Таблица сравнения

ХарактеристикаPUTPATCH
НазначениеПолное замещениеЧастичное обновление
ИдемпотентностьИдемпотентенМожет быть неидемпотентным
Тело запросаПолное представлениеТолько измененные поля
Несуществующий ресурсМожет создатьОбычно ошибка 404
Пропущенные поляУдаляются/обнуляютсяНе меняются
HTTP Статус200/204200/202

Детальный пример

import requests

# Исходные данные
original_user = {
    "id": 1,
    "name": "Иван Петров",
    "email": "ivan@example.com",
    "phone": "+7-900-111-11-11",
    "age": 30,
    "role": "user"
}

print("=== ИСХОДНЫЙ РЕСУРС ===")
print(original_user)

# Сценарий 1: Обновление только email с использованием PUT
print("\n=== PUT: Обновляем только email ===")
put_data = {
    "name": "Иван Петров",
    "email": "new_ivan@example.com",
    "phone": "+7-900-111-11-11",
    "age": 30,
    "role": "user"
    # Нужно передать ВСЕ поля, даже если они не меняются!
}
print(f"PUT данные: {put_data}")
print(f"Результат PUT: ресурс заменён полностью")

# Сценарий 2: Обновление только email с использованием PATCH
print("\n=== PATCH: Обновляем только email ===")
patch_data = {
    "email": "new_ivan@example.com"
    # Передаём только то, что меняется
}
print(f"PATCH данные: {patch_data}")
expected_result = original_user.copy()
expected_result["email"] = "new_ivan@example.com"
print(f"Результат PATCH: {expected_result}")
print("Остальные поля не изменились!")

Идемпотентность

PUT — идемпотентен

Многоткратное выполнение одного и того же PUT запроса даёт одинаковый результат:

# Первый PUT
response1 = requests.put(
    "https://api.example.com/users/1",
    json={"name": "Иван", "email": "ivan@example.com", "age": 30}
)
# Результат: {"id": 1, "name": "Иван", "email": "ivan@example.com", "age": 30}

# Второй PUT с теми же данными
response2 = requests.put(
    "https://api.example.com/users/1",
    json={"name": "Иван", "email": "ivan@example.com", "age": 30}
)
# Результат: то же самое {"id": 1, "name": "Иван", "email": "ivan@example.com", "age": 30}

# Безопасно повторять!
print("PUT идемпотентен: результат всегда одинаков")

PATCH — может быть неидемпотентным

Если операция зависит от текущего состояния, PATCH может быть неидемпотентным:

# Операция 1: инкремент счётчика
# Исходное значение: views = 100
patch_data_1 = {"$increment": {"views": 1}}
# После первого PATCH: views = 101

patch_data_2 = {"$increment": {"views": 1}}
# После второго PATCH: views = 102

# Результаты разные! PATCH неидемпотентен
print("PATCH может быть неидемпотентным!")

Реальные примеры использования

Пример 1: Обновление профиля пользователя

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Optional

app = FastAPI()

class UserUpdate(BaseModel):
    name: Optional[str] = None
    email: Optional[str] = None
    phone: Optional[str] = None

class UserFull(BaseModel):
    id: int
    name: str
    email: str
    phone: str

# Хранилище пользователей
users_db = {
    1: {"id": 1, "name": "Иван", "email": "ivan@example.com", "phone": "+7-900-111-11-11"}
}

# PUT: полное замещение
@app.put("/users/{user_id}")
def update_user_full(user_id: int, user: UserFull):
    """Полное обновление: требует все поля"""
    if user_id not in users_db:
        raise HTTPException(status_code=404, detail="User not found")
    
    # Заменяем весь объект
    users_db[user_id] = user.model_dump()
    return users_db[user_id]

# PATCH: частичное обновление
@app.patch("/users/{user_id}")
def update_user_partial(user_id: int, user_update: UserUpdate):
    """Частичное обновление: только переданные поля"""
    if user_id not in users_db:
        raise HTTPException(status_code=404, detail="User not found")
    
    # Обновляем только переданные поля
    update_data = user_update.model_dump(exclude_unset=True)
    users_db[user_id].update(update_data)
    return users_db[user_id]

# Пример использования:
if __name__ == "__main__":
    # Текущий пользователь
    print(users_db[1])
    # {'id': 1, 'name': 'Иван', 'email': 'ivan@example.com', 'phone': '+7-900-111-11-11'}
    
    # PUT: нужно передать всё
    print("\n=== PUT запрос ===")
    new_user = UserFull(id=1, name="Петр", email="petr@example.com", phone="+7-900-222-22-22")
    # Результат: {'id': 1, 'name': 'Петр', 'email': 'petr@example.com', 'phone': '+7-900-222-22-22'}
    
    # PATCH: можно передать только изменившиеся поля
    print("\n=== PATCH запрос ===")
    update = UserUpdate(email="new_email@example.com")
    # Результат: {'id': 1, 'name': 'Петр', 'email': 'new_email@example.com', 'phone': '+7-900-222-22-22'}

Пример 2: JSON Patch (RFC 6902)

Для сложных операций можно использовать JSON Patch:

from jsonpatch import JsonPatch
import json

# Исходный документ
original = {
    "name": "Иван",
    "email": "ivan@example.com",
    "address": {"city": "Москва", "street": "Ленина"},
    "hobbies": ["программирование", "чтение"]
}

# JSON Patch — дескриптор изменений
patch = [
    {"op": "replace", "path": "/email", "value": "new_ivan@example.com"},
    {"op": "replace", "path": "/address/city", "value": "СПб"},
    {"op": "add", "path": "/hobbies/-", "value": "спорт"},
    {"op": "remove", "path": "/address/street"}
]

# Применяем патч
patch_obj = JsonPatch(patch)
result = patch_obj.apply(original)

print(json.dumps(result, indent=2, ensure_ascii=False))
# {
#   "name": "Иван",
#   "email": "new_ivan@example.com",
#   "address": {"city": "СПб"},
#   "hobbies": ["программирование", "чтение", "спорт"]
# }

Когда использовать что

# ✓ Используй PUT когда:
# - Полностью заменяешь ресурс
# - Есть уверенность, что переданы все обязательные поля
# - Нужна идемпотентность (безопасно повторять)

requests.put("/api/articles/1", json={
    "title": "Новый заголовок",
    "content": "Новое содержание",
    "author_id": 5,
    "category": "tech"
    # Все поля обязательны!
})

# ✓ Используй PATCH когда:
# - Обновляешь только некоторые поля
# - Не хочешь передавать неменяющиеся данные
# - Хочешь экономить пропускную способность

requests.patch("/api/articles/1", json={
    "title": "Новый заголовок"
    # Остальное не меняется
})

Важные замечания

# 1. PUT требует полного представления
# Если забыть поле, оно будет удалено
requests.put("/users/1", json={"name": "Иван"})  # email исчезнет!

# 2. PATCH безопаснее для мобильных приложений
# Меньше данных передаётся по сети
# Но может быть неидемпотентным

# 3. 409 Conflict возможен при PATCH
# Если ресурс изменился с момента получения
response = requests.patch("/users/1", json={"email": "new@example.com"})
if response.status_code == 409:
    print("Ресурс изменился, перечитай и повтори")

Заключение

PUT — это полная замена ресурса. Используй, когда отправляешь полное представление и хочешь быть уверен в идемпотентности.

PATCH — это частичное обновление. Используй, когда меняешь только некоторые поля и хочешь сэкономить трафик.

Выбор между ними зависит от контекста и требований API. Большинство modern API предпочитает PATCH для пользовательских операций, потому что он более гибкий.