沉默的注册杀手:当 Auto-Formatter 与 Linter 碰撞时

发布: (2026年1月12日 GMT+8 10:35)
5 min read
原文: Dev.to

抱歉,我需要您提供要翻译的具体文本内容(文章正文),才能为您进行简体中文翻译。请把需要翻译的文字粘贴在这里,我会在保持原有格式、代码块和链接不变的前提下完成翻译。

意外的注册错误

一个功能已经发布,手动测试看起来“还行”,代码随后进入了预发布环境。
不久之后,用户访问注册接口时收到通用的 500 Internal Server Error
服务器日志显示:

TypeError: CustomUser() got unexpected keyword arguments: 'password2'

代码乍看之下是正确的,但一系列“好心”的工具引入了一个微妙的 bug。

格式化失误导致 TypeError

在项目中,注册使用了 ModelSerializer,并添加了 password2 字段用于“确认密码”校验。一次清理或自动格式化时,删除传入数据中 password2 的那行代码不小心被移动或被注释掉了。

def create(self, validated_data):
    # Extract the main password
    password = validated_data.pop("password")

    # Oops – `password2` was left in the dictionary!
    # validated_data might look like:
    # {"email": "user@example.com", "password2": "secret123"}

    user = User.objects.create_user(**validated_data)  # <-- crash point

    user.set_password(password)
    user.save()
    return user

由于 validated_data 直接传给了 create_user,Django 试图把 password2 作为关键字参数传递给 User 模型,而该模型并没有此字段,导致出现 TypeError。界面只显示了一个模糊的 “Server Error”,让用户束手无策。

Linter 冲突

为了捕获此类“脏”字典,团队通过 pre‑commit hooks 引入了 Ruff。当开发者尝试通过弹出变量来修复错误时,Ruff 报告:

password2 = validated_data.pop("password2")
F841 Local variable 'password2' is assigned to but never used

本能的反应是关闭该规则:

- id: ruff
  args: ["--fix", "--unfixable=F841"]

然而,--unfixable 只会阻止 Ruff 自动修复该问题;它 不会 忽略错误,因此 pre‑commit hook 仍然会失败。

正确弹出未使用数据的方法

“Dirty” 修复(触发 F841)

password2 = validated_data.pop("password2")

Clean 修复(无变量赋值 → 无 linter 错误)

validated_data.pop("password2", None)

Pythonic 修复(下划线表示有意未使用的变量)

_password2 = validated_data.pop("password2")

下划线前缀告诉阅读者和 linter,这个变量是有意被忽略的。

Source:

推荐的 Ruff 配置用于 Django 项目

与其与 linter 抗争,不如把它配置成配合你工作的工具。下面是一份经过实战检验的 ruff.toml,在严格性和 Django 特有的实际需求之间取得了平衡。

# ruff.toml
target-version = "py312"
line-length = 88

[lint]
# 启用广泛的规则集
select = [
    "F",   # Pyflakes(未使用的变量等)
    "E",   # Error(标准风格)
    "W",   # Warning
    "I",   # Isort(导入排序)
    "DJ",  # Django‑specific rules(Django 专用规则)
    "B",   # Bugbear(常见设计缺陷)
    "SIM", # Simplicity(更简洁的代码)
]

# 忽略对团队来说噪音过大的规则
ignore = [
    "DJ001", # 在 manager 中有时需要直接导入 User
]

[lint.per-file-ignores]
# 忽略包初始化文件中的未使用导入
"__init__.py" = ["F401"]
# 忽略自动生成的迁移文件中的行长度和未使用导入警告
"**/migrations/*" = ["E501", "F401"]

[format]
quote-style = "double"
indent-style = "space"

额外的 Django 小技巧

始终使用 get_user_model()(或类似 'myapp.CustomUser' 的字符串引用)来获取模型,而不是直接导入具体的 User 类。这样可以避免循环导入和硬编码问题。

要点

  • Linting 是护栏,而不是障碍。 当 Ruff 标记未使用的变量时,通常是在警告可能导致下游逻辑出错的“垃圾”数据。
  • 在不赋值的情况下弹出未使用的键 (validated_data.pop("password2", None)) 能满足 linter 并防止 TypeError
  • 配置你的 linter 而不是静音它;经过精心调校的 ruff.toml 能在尊重 Django 特性的同时保持代码库整洁。
  • 显式优于隐式。 清晰的数据处理使函数保持纯粹,日志更简洁,后期维护更容易。

通过使用干净的弹出模式和合理的 Ruff 配置,团队消除了注册崩溃,满足了 linter 的要求,并恢复了流畅的用户体验。

Back to Blog

相关文章

阅读更多 »

天才大师与他高效的笨蛋仆人

“为什么!为什么!呃!” 当我看到Sanni抓住一把自己的头发并用力拉扯时,我的嘴角上扬。我靠在椅子上,注视着她眼中凶狠的光芒……