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/