bigywater.tech
Django · 14

AppConfig.ready() 的执行时机与常见陷阱

ready() 是放信号注册、启动检查的标准位置,但它被调用的时机比想象中多:每条管理命令、runserver 的两个进程。这篇把这些情形逐个数出来,并复现最常见的坑——在 ready() 里查数据库。

顺序
按 INSTALLED_APPS 顺序调用;调用时所有 app 的模型都已加载
2 次
runserver 默认(自动重载)下 ready() 的调用次数,来自 2 个进程
migrate 也会失败
ready() 里查还没建的表,连 migrate 自己都跑不起来

调用的时机与顺序

实测 · 本机执行输出
   a.ready()  b 的模型已加载?True  apps.ready=False
   b.ready()
   urls 被导入

两个 app a、b,各自的 ready() 打点。a 先、b 后;a 的 ready() 里 b 的模型已经加载。apps.ready 要等所有 ready() 都跑完才变 True。

哪些命令会触发

实测 · 本机执行输出
  manage.py check                              a.ready() 调用 1 次
  manage.py showmigrations                     a.ready() 调用 1 次
  manage.py makemigrations --check --dry-run   a.ready() 调用 1 次
  manage.py test --noinput                     a.ready() 调用 1 次
  manage.py help                               a.ready() 调用 1 次

每个命令启动时都会 django.setup(),所以 ready() 里的耗时或副作用在任何命令下都发生。

runserver 为什么执行两次

实测 · 本机执行输出
  runserver(默认,自动重载)           a.ready() 2 次,来自 2 个不同进程;urls 导入 1 次(pid: 18341, 18342)
  runserver --noreload         a.ready() 1 次,来自 1 个不同进程;urls 导入 1 次(pid: 18368)

默认的自动重载让 runserver 启动一个监视进程和一个真正服务的子进程,两个进程都会 django.setup(),所以 ready() 里的一次性逻辑(比如启动后台线程、发通知)会执行 2 次。--noreload 只有一次。用 gunicorn 多个 worker 时每个 worker 进程各一次(本文没有测)。

在 ready() 里查数据库

实测 · 本机执行输出
  全新库上 python manage.py migrate(rc=1),最后一行: django.db.utils.OperationalError: no such table: lab_item
  同一个库上 check(表还不存在),最后一行: django.db.utils.OperationalError: no such table: lab_item

这是最常见的坑:ready() 里 Item.objects.count() 在全新数据库上会让 migrate 报错。ready() 里应避免访问数据库;确实需要,放到信号、第一次请求、或独立的管理命令里。

分步演示:ready() 何时被调用

AppConfig.ready()
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。runserver 的次数通过在 ready() 里向日志文件追加 pid 统计
    • 没有验证:gunicorn / uvicorn 多 worker 下的次数、ready() 里访问数据库时 Django 6.1 是否还会给出运行时警告(这次实验只看到了报错)、ready() 抛异常时各命令的行为
    京ICP备2026067502号-1京公网安备11010502063487号