DJANGO_SETTINGS_MODULE 是怎么变成 settings.XXX 的?为什么在模块顶层写 X = settings.DEBUG 有时会「不跟着变」?这篇用一个真实项目,一条一条验证 settings 的惰性加载、大小写规则和覆盖行为。
[导入与读取]
from django.conf import settings 之后,proj.settings 已导入? False
settings._wrapped 类型: object
读取 settings.DEBUG 之后,proj.settings 已导入? True
settings._wrapped 类型: Settings
settings.SETTINGS_MODULE = proj.settingssettings 是 LazySettings:导入时里面只有占位,第一次属性访问才读环境变量、导入设置模块。所以可以先 import 再决定用哪个设置模块(比如在 manage.py 里先 setdefault 环境变量)。
未设置环境变量: django.core.exceptions.ImproperlyConfigured: Requested setting DEBUG, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings. DJANGO_SETTINGS_MODULE=nope.settings: ModuleNotFoundError: No module named 'nope'
没配置和配错模块名是两种不同的异常,排查时看异常类型就能分清:前者是 ImproperlyConfigured,后者是普通的 ModuleNotFoundError。
[大小写与默认值]
settings.MY_UPPER = 2
hasattr(settings, "my_lower") = False
settings.my_lower → AttributeError 'Settings' object has no attribute 'my_lower'
全局默认设置个数(global_settings 里全大写的名字): 150
proj.settings 里显式写了的设置个数: 15
DEFAULT_CHARSET(没写,取默认) = utf-8 DEBUG(写了 True) = True
manage.py diffsettings 输出 13 行,前 6 行:
ALLOWED_HOSTS = ['*']
BASE_DIR = PosixPath('<实验目录>') ###
DATABASES = {'default': {'ENGINE': 'django.db.backends.sqlite3', 'NAME': PosixPath('<实验目录>')}}
DEBUG = True
INSTALLED_APPS = ['django.contrib.contenttypes']
MY_UPPER = 2 ###只有全大写的名字会被读成设置(设置模块里的小写变量、类实例上的属性都不是)。没写的设置来自 django.conf.global_settings。manage.py diffsettings 只列出与默认值不同的项,输出里带 ### 的是默认里不存在的自定义设置。
[改写与覆盖]
导入 proj.extra 时读到的 STALE = True ,fresh() = True
override_settings(DEBUG=False) 内: settings.DEBUG = False extra.STALE = True extra.fresh() = False
退出 with 之后: settings.DEBUG = True
直接赋值 settings.DEBUG = False 之后: fresh() = False (进程内一直生效,没有还原)override_settings 是测试里的正规做法,退出时还原。要记住的陷阱:在模块顶层读取设置(X = settings.DEBUG)只读一次,之后的覆盖影响不到它——需要动态值时,在函数里读,或者用 SimpleLazyObject 之类延迟到使用时。直接赋值 settings.DEBUG = False 在进程内一直生效,不要在生产代码里这么做。
第一次 configure OK, DEBUG = True
第二次 configure → RuntimeError: Settings already configured.
INSTALLED_APPS 写成字符串: django.core.exceptions.ImproperlyConfigured: The INSTALLED_APPS setting must be a list or a tuple.不依赖设置文件、在脚本里用 Django 时可以调用 settings.configure(...),但只能调用一次。