
在 Django 开发中,用 DoesNotExist 异常捕获“记录已存在”来实现业务校验(如防重复提交)虽能工作,但违背 Python 的显式、可读与高效原则;推荐改用 get_or_create() 或数据库唯一约束,让异常回归其本职——处理真正意外的错误。
在 django 开发中,用 `doesnotexist` 异常捕获“记录已存在”来实现业务校验(如防重复提交)虽能工作,但违背 python 的显式、可读与高效原则;推荐改用 `get_or_create()` 或数据库唯一约束,让异常回归其本职——处理真正意外的错误。
在 Python 和 Django 生态中,“异常用于异常情况”是一条核心设计哲学。当一个操作的结果是业务逻辑中明确预期且高频发生的分支(例如:用户尝试重复提交表单),此时刻意触发并捕获 DoesNotExist 异常,本质上是将控制流逻辑伪装成错误处理——这不仅降低代码可读性,还会带来不必要的性能开销(异常实例化、栈展开、GC 压力),也与 Python 的“显式优于隐式”(PEP 20)和 Django 的“约定优于配置”理念相悖。
✅ 推荐方案一:使用 get_or_create()(最 Pythonic)
get_or_create() 是 Django 提供的原子性方法,专为“查+存”场景设计,返回 (obj, created) 元组,语义清晰、无异常干扰:
from django.http import HttpResponseBadRequest
def submit_view(request):
instance_id = request.POST.get('id')
user = request.logged_in_user
# 原子性查询或创建,不抛异常
my_row, created = models.MyModel.objects.get_or_create(
id=instance_id,
user=user,
defaults={'other_field': 'default_value'} # 仅创建时生效
)
if not created:
return HttpResponseBadRequest("Already submitted")
# ✅ 正常流程:继续处理新提交(如发送通知、更新状态等)
my_row.process()
my_row.save()
return JsonResponse({"status": "success"})✅ 优势:零异常开销、意图明确、线程安全(配合数据库约束更可靠)、符合 Python 惯例。
✅ 推荐方案二:数据库层强制唯一约束 + IntegrityError
若业务要求强一致性(如防止并发重复创建),应在模型层定义唯一约束,并在视图中捕获 IntegrityError ——此时异常才真正代表“违反数据完整性”的意外状况:
# models.py
class MyModel(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
# 确保每个用户只能有一条记录
class Meta:
constraints = [
models.UniqueConstraint(fields=['user'], name='unique_per_user')
]
# views.py
from django.db import IntegrityError
from django.http import HttpResponseBadRequest
def submit_view(request):
try:
my_row = models.MyModel.objects.create(
id=request.POST.get('id'),
user=request.logged_in_user
)
except IntegrityError:
return HttpResponseBadRequest("Already submitted") # 真正的“意外冲突”
# 继续正常流程...
return JsonResponse({"status": "created"})⚠️ 注意:
IntegrityError是数据库级异常,比DoesNotExist更接近“真实错误”,但仍建议仅用于兜底(因get_or_create已覆盖大部分场景)。
❌ 为什么不推荐 try/except DoesNotExist?
原始写法的问题在于:
-
语义错位:
DoesNotExist表示“查无此物”,但业务需求实为“禁止重复”——这是主动校验,而非被动失败; - 性能损耗:每次未命中都会触发完整异常机制,比布尔判断慢 10–100 倍;
- 可维护性差:后续阅读者需逆向推理“为何要捕获这个异常?”而非直观理解业务逻辑;
-
掩盖风险:若未来
MyModel增加其他字段导致get()条件变更,异常可能误判,而get_or_create的defaults或约束能更早暴露问题。
总结:异常使用的黄金准则
| 场景 | 是否应抛异常 | 原因 |
|---|---|---|
| 用户重复提交、邮箱已注册等常见业务限制 | ❌ 否 | 应用层条件判断或 get_or_create
|
| 数据库违反唯一/非空约束等数据完整性破坏 | ✅ 是(IntegrityError) |
真实意外,需监控与告警 |
| 网络超时、磁盘满、第三方服务宕机等系统级故障 | ✅ 是(自定义异常) | 不可控,需统一错误处理 |
记住:Django 的异常机制不是 switch-case 的替代品。把业务分支交给清晰的布尔逻辑,把真正的意外留给异常处理器——这才是既 Pythonic 又 Djangonic 的实践。


















