Django的settings文件部分源碼分析
在編寫Django項目的過程中, 其中一個非常強大的功能就是我們可以在settings文件配置許多選項來完成我們預期的功能, 並且這些配置還必須大寫, 否則就不會生效. 此外, Django自身還有一套更詳細的配置, 那Django是如何做到用户配置了相關配置就使用用户的配置, 否則就使用自己默認的配置. 帶着這樣的疑問, 去查看了用户配置項相關的源碼部分.
過程分析
首先啓動Django項目, 一般Django都是通過python manage.py runserver這句命令啓動的. 從這個入口函數出發, 主要執行了下面3句話.
if __name__ == "__main__":
# settings文件配置到環境變量
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "auth_learn.settings")
from django.core.management import execute_from_command_line
# 解析並執行命令行參數
execute_from_command_line(sys.argv)
上面會將我們用户的配置文件(項目下的settings文件)設置到當前環境變量裏面.
順着代碼的流程下去.
def execute_from_command_line(argv=None):
"""
運行了一個命令管理工具, 將命令行的參數傳到這個對象中, 並執行
"""
utility = ManagementUtility(argv)
utility.execute()
繼續往下運行
def execute(self):
# 解析命令行參數列表的第一個參數
subcommand = self.argv[1]
parser = CommandParser(None, usage="%(prog)s subcommand [options] [args]", add_help=False)
options, args = parser.parse_known_args(self.argv[2:])
handle_default_options(options)
...
try:
# 這句話就是重點了, 開始加載app
settings.INSTALLED_APPS
except ImproperlyConfigured as exc:
self.settings_exception = exc
...
看到這, 終於看到了和settings文件相關的代碼了. 跟進去.
settings = LazySettings()
看到了settings是一個懶加載(延時加載)的LazySettings類的實例對象.
繼續跟進LazySettings的定義
class LazySettings(LazyObject):
def _setup(self, name=None):
settings_module = os.environ.get(ENVIRONMENT_VARIABLE)
self._wrapped = Settings(settings_module)
def __getattr__(self, name):
if self._wrapped is empty:
self._setup(name)
val = getattr(self._wrapped, name)
self.__dict__[name] = val # 用到了直接緩存到__dict__裏面
return val
...
發現LazySettings類繼承自LazyObject, 本身並沒有__init__方法, 所以繼續往父類跟進
_wrapped = None
def __init__(self):
self._wrapped = empty
父類什麼也沒有定義, 就是一個空對象. 所以settings對象初始化後什麼屬性也沒有, 這時候Django調用settings.INSTALLED_APPS這句話就是懶加載的核心. 所謂懶加載,就是在需要用到的時候再加載. 一般手段有代理類,線程... Django中使用 LazyObject 代理類。加載函數是 _setup 函數,當獲取屬性時才會去加載。
LazySettings 繼承自 LazyObject 類,它重寫了 __getattr__ 和 __setattr__ 方法,那麼在調用 settings.INSTALLED_APPS 時,就會觸發 __getattr__ 這個雙下方法. 我們知道, 初始化的時候, settings對象就是一個empty空對象.這就會去調用加載函數_setup函數
ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
def _setup(self, name=None):
settings_module = os.environ.get(ENVIRONMENT_VARIABLE)
self._wrapped = Settings(settings_module)
第一句話, setting_module就是從環境變量中獲取我們用户自定義的配置文件, 這在剛啓動manage.py文件就已經定義好了. 接下來就去實例化一個Settings對象. 從這就可以得出結論通過settings對象的屬性都是從_wrapped這個私有屬性獲取來的, 或者説是從Settings實例對象中獲取來的.
繼續跟進Settings類的源碼.
# global_settings就是一個django內部的全局配置文件
from django.conf import global_settings
class Settings(object):
def __init__(self, settings_module):
# 這句話就是遍歷全局配置, 將所有的屬性添加到settings對象中
for setting in dir(global_settings):
# 這裏也説明了為什麼屬性需要大寫
if setting.isupper():
setattr(self, setting, getattr(global_settings, setting))
# store the settings module in case someone later cares
self.SETTINGS_MODULE = settings_module
# 這裏就是動態的將我們用户的自定義配置文件模塊導入
mod = importlib.import_module(self.SETTINGS_MODULE)
self._explicit_settings = set()
# 遍歷用户自定義配置文件
for setting in dir(mod):
# 如果我們配置的屬性不是大寫, 就會無效
if setting.isupper():
# 獲取用户的配置屬性
setting_value = getattr(mod, setting)
# 將我們配置的屬性添加到settings配置文件中, 或者覆蓋掉
# Django默認的配置屬性.
setattr(self, setting, setting_value)
self._explicit_settings.add(setting)
到了這, 開頭的問題也就解決了. 來一句話總結, 就是Django先加載自己的配置文件, 然後再加載用户的配置文件覆蓋掉默認的屬性, 保存到一個settings延時加載的對象中. 配置文件大寫的原因也只是因為源碼只處理全大寫的屬性而已.
參考
http://www.hongweipeng.com/index.php/archives/1370/