Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
В чём разница между 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())
Таблица сравнения
| Характеристика | PUT | PATCH |
|---|---|---|
| Назначение | Полное замещение | Частичное обновление |
| Идемпотентность | Идемпотентен | Может быть неидемпотентным |
| Тело запроса | Полное представление | Только измененные поля |
| Несуществующий ресурс | Может создать | Обычно ошибка 404 |
| Пропущенные поля | Удаляются/обнуляются | Не меняются |
| HTTP Статус | 200/204 | 200/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 для пользовательских операций, потому что он более гибкий.