django源码解读: setting懒加载

前言

上次我们了解了django启动原理,细心的朋友可能发现django中的setting配置文件加载时懒加载,接下来我们了解下setting的懒加载

懒加载

我们从manager.py进入management/__init__.py,我们可以看到导入有这个from django.conf import settings,我们进入setting后就可以看到懒加载源码(为了方便理解拿掉部分代码)

class LazySettings(LazyObject):
    """
    全局Django设置或自定义设置对象的惰性代理。
    用户可以在使用之前手动配置设置。 除此以外,Django使用DJANGO_SETTINGS_MODULE指向的设置模块。
    """

    def _setup(self, name=None):
        """
        加载环境变量指向的设置模块。 这个如果用户尚未使用,则在第一次需要设置时使用手动配置的设置。
        """
        # 这个是获取我们manager.py里的os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
        settings_module = os.environ.get(ENVIRONMENT_VARIABLE)
        if not settings_module:
            # 如果没有则抛出异常
            pass

        self._wrapped = Settings(settings_module)

    def __getattr__(self, name):
        # 返回设置的值并将其缓存在self .__ dict__
        if self._wrapped is empty:
            self._setup(name)
        val = getattr(self._wrapped, name)
        self.__dict__[name] = val
        return val

    def __setattr__(self, name, value):
        """
        设置设置值。 如果_wrapped发生更改,则清除所有缓存的值(@override_settings执行此操作),或者在设置时清除单个值
        """
        if name == '_wrapped':
            self.__dict__.clear()
        else:
            self.__dict__.pop(name, None)
        super().__setattr__(name, value)

    def __delattr__(self, name):
        """
        删除设置并根据需要从缓存中清除它。
        """
        super().__delattr__(name)
        self.__dict__.pop(name, None)
    
    # 其他方法

settings = LazySettings()

懒加载:在需要用到的时候再加载。我们一般用代理类,线程。在django 中使用 LazyObject 代理类。加载函数是 _setup 函数,当获取属性时才会去加载。

LazySettings重写了 __getattr__ , __setattr__ ,__delattr__和_setup方法,那么在调用 settings.INSTALLED_APPS 时,会通过其自定义的 __getattr__ 方法实现,从中可以看出,所有属性都是从 self._wrapped 也就是 Settings(settings_module) 这个实例中取得的。

setting文件加载

首先通过os.environ.get(ENVIRONMENT_VARIABLE)获取当前环境变量中的DJANGO_SETTINGS_MODULE,DJANGO_SETTINGS_MODULE 是我们在manager.py中设置的(os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings'))。通过getter和setter,对 settings对象的操作转到其私有成员 self._wrapped 对象的调用上,这里在第一次使用settings对象时,将其私有成员 self._wrapped 初始化为 Settings 类实例:

class Settings:
    def __init__(self, settings_module):
        # 从全局设置更新此字典---默认配置
        for setting in dir(global_settings):
            if setting.isupper():
                setattr(self, setting, getattr(global_settings, setting))

        # 存储设置模块
        self.SETTINGS_MODULE = settings_module
        # # 加载配置文件 settings.py
        mod = importlib.import_module(self.SETTINGS_MODULE)

        tuple_settings = (
            "INSTALLED_APPS",
            "TEMPLATE_DIRS",
            "LOCALE_PATHS",
        )
        self._explicit_settings = set()
        # 将用户配置的属性覆盖默认配置
        for setting in dir(mod):
            if setting.isupper():
                setting_value = getattr(mod, setting)

                if (setting in tuple_settings and
                        not isinstance(setting_value, (list, tuple))):
                    # 抛出错误
                    pass
                setattr(self, setting, setting_value)
                self._explicit_settings.add(setting)

        if not self.SECRET_KEY:
            # 如果没有SECRET_KEY抛出错误
            pass

        if hasattr(time, 'tzset') and self.TIME_ZONE:
            # 验证时区。
            zoneinfo_root = Path('/usr/share/zoneinfo')
            zone_info_file = zoneinfo_root.joinpath(*self.TIME_ZONE.split('/'))
            if zoneinfo_root.exists() and not zone_info_file.exists():
                raise ValueError("Incorrect timezone setting: %s" % self.TIME_ZONE)
            # 将时区信息移到os.environ中
            os.environ['TZ'] = self.TIME_ZONE
            time.tzset()

首先通过 global_settings 来设置其属性,接着读取 project.settings设置其属性,主要有 INSTALLED_APPS,TEMPLATE_DIRS,LOCALE_PATHS这几个key,默认都是空元组。源码中使用 importlib.import_module 来加载用户自定义的配置模块,完成全局实例的初始化。项目实例化后,配置懒加载也就完成了,程序就回到 execute 函数,接下去就是运行 django.setup() 函数了。

execute函数的部分代码

def execute(self):
    try:
        subcommand = self.argv[1]
    except IndexError:
        subcommand = 'help'  # Display help if no arguments were given.

    # 预处理选项以提取--settings和--pythonpath
    parser = CommandParser(usage='%(prog)s subcommand [options] [args]', add_help=False, allow_abbrev=False)
    parser.add_argument('--settings')
    parser.add_argument('--pythonpath')
    parser.add_argument('args', nargs='*')  # catch-all
    try:
        options, args = parser.parse_known_args(self.argv[2:])
        handle_default_options(options)
    except CommandError:
        pass

    try:
        settings.INSTALLED_APPS
    except ImproperlyConfigured as exc:
        self.settings_exception = exc
    except ImportError as exc:
        self.settings_exception = exc

    if settings.configured:

        if subcommand == 'runserver' and '--noreload' not in self.argv:
            try:
                # 对错误进行捕捉
                autoreload.check_errors(django.setup)()
            except Exception:
                pass

        else:
            django.setup()

"INSTALLED_APPS","TEMPLATE_DIRS","LOCALE_PATHS"的解释如下:

  1. INSTALLED_APPS,它表示项目注册的app 处于激活状态。
  2. TEMPLATE_DIRS,它表示模板文件的处处路径。
  3. LOCALE_PATHS,它表示Django将在这些路径中查找包含实际翻译文件的 <locale_code>/LC_MESSAGES 目录

懒加载实现

class LazyProxy:
    def __init__(self, cls, *args, **kwargs):
        self.__dict__['_cls'] = cls
        self.__dict__['_params'] = args
        self.__dict__['_kwargs'] = kwargs
        self.__dict__['_obj'] = None

    def __getattr__(self, item):
        if self.__dict__['_obj'] is None:
            self._init_obj()
        return getattr(self.__dict__['_obj'], item)

    def __setattr__(self, key, value):
        if self.__dict__['_obj'] is None:
            self._init_obj()

        setattr(self.__dict__['_obj'], key, value)

    def _init_obj(self):
        self.__dict__['_obj'] = object.__new__(self.__dict__['_cls'])
        self.__dict__['_obj'].__init__(*self.__dict__['_params'], **self.__dict__['_kwargs'])


class LazyInit:
    def __new__(cls, *args, **kwargs):
        return LazyProxy(cls, *args, **kwargs)


class A(LazyInit):
    def __init__(self, x):
        print("Init A")
        self.x = x

学习链接

Python之实例懒加载实现

python中的懒加载模块

 

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木子林_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值