从Django源码学习了模块的运行时导入importlib.import_module()、按名取模块属性getattr()、按名设置模块属性setattr()

想跟一下Django源码,通过项目manage.py的execute_from_command_lie(sys.argv)函数,一路进行代码跟踪,最终在django/core/management/__init__.py的类ManagementUtility的方法execute()方法中碰了壁,死活没找到类LazySettings的属性INSTALLED_APPS的属性初始化位置,虽然通过grep关键字查找找到了"应该"是来自于项目目录DirPcDjg0725/settings.py中的要安装的应用程序列表INSTALLED_APPS,但就想搞清楚是怎么作为类LazySettings的属性的?

    单纯阅读Django代码是相当吃力,于是在pycharm中建立了一个Django项目,通过跟踪函数调用来找出"类LazySettings的属性INSTALLED_APPS"的初始化位置。但发现诸如"from django.conf import settings"这样的导模块语句,将断点设置在django/conf/__init__.py中时死活不仅断不到,在逐行调试时所有在"from django.conf import settings"中的执行都会在Debugger窗口显示帧不可用"<frame not available>",pycharm是和谐的2020.1.1,不知道其他版本可不可以断得进去。

    无奈只能通过控制台输出变量来研究了:

 

在虚拟环境下调用Django项目的manage.py模块并传参"runserver 8000":

(venv_3.8) haypin@ubt:~/DirProj0721$ python3.8 manage.py runserver 8000

 

        在manage.py中导入了~/DirProj0721/venv_3.8/lib/python3.8/site-packages/django/core/management/包的global函数execute_from_command_line,然后将参数sys.argv=["runserver", "8000"]传给并调用了execute_from_command_line(sys.argv);

        django/core/management/包的__init__.py中global函数execute_from_command_line(argv)定义了类ManagementUtility(object)的一个实例对象:utility = ManagementUtility(argv),并调用了类ManagementUtility的实例方法execute(),以下是该方法的定义:

django/core/management/__init__.py:

    def execute(self):
        """
        Given the command-line arguments, figure out which subcommand is being
        run, create a parser appropriate to that command, and run it.
        """
        try:
            subcommand = self.argv[1]
        except IndexError:
            subcommand = 'help'  # Display help if no arguments were given.

        # Preprocess options to extract --settings and --pythonpath.
        # These options could affect the commands that are available, so they
        # must be processed early.
        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:])
            print(options, '---', args) #打印看看
            handle_default_options(options)
        except CommandError:
            pass  # Ignore any option errors at this point.

        try:
            settings.INSTALLED_APPS
            print(settings.INSTALLED_APPS) #打印看看
        except ImproperlyConfigured as exc:
            self.settings_exception = exc
        except ImportError as exc:
            self.settings_exception = exc

        if settings.configured:
            # Start the auto-reloading dev server even if the code is broken.
            # The hardcoded condition is a code smell but we can't rely on a
            # flag on the command class because we haven't located it yet.
            if subcommand == 'runserver' and '--noreload' not in self.argv:
                try:
                    autoreload.check_errors(django.setup)()
                except Exception:
                    # The exception will be raised later in the child process
                    # started by the autoreloader. Pretend it didn't happen by
                    # loading an empty list of applications.
                    apps.all_models = defaultdict(dict)
                    apps.app_configs = {}
                    apps.apps_ready = apps.models_ready = apps.ready = True

                    # Remove options not compatible with the built-in runserver
                    # (e.g. options for the contrib.staticfiles' runserver).
                    # Changes here require manually testing as described in
                    # #27522.
                    _parser = self.fetch_command('runserver').create_parser('django', 'runserver')
                    _options, _args = _parser.parse_known_args(self.argv[2:])
                    for _arg in _args:
                        self.argv.remove(_arg)

            # In all other cases, django.setup() is required to succeed.
            else:
                django.setup()

        self.autocomplete()

        if subcommand == 'help':
            if '--commands' in args:
                sys.stdout.write(self.main_help_text(commands_only=True) + '\n')
            elif not options.args:
                sys.stdout.write(self.main_help_text() + '\n')
            else:
                self.fetch_command(options.args[0]).print_help(self.prog_name, options.args[0])
        # Special-cases: We want 'django-admin --version' and
        # 'django-admin --help' to work, for backwards compatibility.
        elif subcommand == 'version' or self.argv[1:] == ['--version']:
            sys.stdout.write(django.get_version() + '\n')
        elif self.argv[1:] in (['--help'], ['-h']):
            sys.stdout.write(self.main_help_text() + '\n')
        else:
            self.fetch_command(subcommand).run_from_argv(self.argv)

其中直接执行了settings.INSTALLED_APPS属性,变量settings从django.conf导入:

from django.conf import settings

定义在django/conf/__init__.py中,是一个LazySettings类实例

settings = LazySettings()

是在类LazySettings的_setup()方法中找到了端倪:

class LazySettings(LazyObject):
    """
    A lazy proxy for either global Django settings or a custom settings object.
    The user can manually configure settings prior to using them. Otherwise,
    Django uses the settings module pointed to by DJANGO_SETTINGS_MODULE.
    """
    def _setup(self, name=None):
        """
        Load the settings module pointed to by the environment variable. This
        is used the first time settings are needed, if the user hasn't
        configured settings manually.
        """
        '''20200725:加载由环境变量指向的settings模块。在第一次settings被需要时被使用,如果用户没有手动配置settings的话。
        '''
        print('执行了类LazySettings._setup()方法') #打印看看
        settings_module = os.environ.get(ENVIRONMENT_VARIABLE) #ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE"
        print("打印'os.environ.get(ENVIRONMENT_VARIABLE)':%s"%settings_module) #20200725:打印出'DirPcDjg0725.settings'
                #也就是项目目录下的settings.py的文件名
        if not settings_module:
            desc = ("setting %s" % name) if name else "settings"
            raise ImproperlyConfigured(
                "Requested %s, but settings are not configured. "
                "You must either define the environment variable %s "
                "or call settings.configure() before accessing settings."
                % (desc, ENVIRONMENT_VARIABLE))

        self._wrapped = Settings(settings_module) #创建Settings类实例,传参'DirPcDjg0725.settings',
                # 并且DirPcDjg0725/settings.py中确实定义有global变量INSTALLED_APPS
        print('打印self.INSTALLED_APPS:%s' % self.INSTALLED_APPS) #20200725:有了self.INSTALLED_APPS属性

LazySettings类的属性_wrapped是创建了一个Settings类实例并传参'DirPcDjg0725.settings'

继续看类Settings的定义:

class Settings:
    def __init__(self, settings_module):
        # update this dict from global settings (but only for ALL_CAPS settings)
        for setting in dir(global_settings):
            if setting.isupper():
                setattr(self, setting, getattr(global_settings, setting)) #将global_settings模块中大写的属性设置成
                        #类Settings的属性,属性名字和属性值都一样
                        #getattr(module-name, attr-name)、setattr(self, attr-name, attr-value)

        # store the settings module in case someone later cares
        self.SETTINGS_MODULE = settings_module #传入参数settings_module是用户项目目录的settings.py模块名DirPcDjg0725.settings

        mod = importlib.import_module(self.SETTINGS_MODULE) #调用内建模块importlib的import_module()方法
            # 导入DirPcDjg0725/settings.py模块,返回mod是模块对象
        print('打印type(mod)=%s, mod=%s' % (type(mod), mod)) #<class 'module> <module 'DirPcDjg0725.settings'>
        tuple_settings = (
            "INSTALLED_APPS",
            "TEMPLATE_DIRS",
            "LOCALE_PATHS",
        )
        self._explicit_settings = set() #创建一个空的集合对象
        for setting in dir(mod): #遍历module 'DirPcDjg0725.settings'模块属性
            if setting.isupper():
                setting_value = getattr(mod, setting) #获取module 'DirPcDjg0725.settings'模块属性

                if (setting in tuple_settings and
                        not isinstance(setting_value, (list, tuple))): #属性名是tuple_settings中的属性并且属性值不是列表或元祖
                    raise ImproperlyConfigured("The %s setting must be a list or a tuple. " % setting)
                setattr(self, setting, setting_value) #为类设置属性setattr(self, attr-name, attr-value),
                        # 其中就包括了属性"INSTALLED_APPS"
                print("为类Settings实例setattr()了%s属性,值为%s" % (setting, setting_value))
                self._explicit_settings.add(setting) #为类Setting的集合属性_explicit_settings增加属性名setting的元素

嘿嘿,原来是肿么回事,在类Settings的构造函数中导入了DirPcDjg0725/settings.py模块,并将模块的列表或元组属性,其中自然包括了global变量INSTALLED_APPS

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
getattr(module-name, attr-name)

设置成类Settings的属性

setattr(self, attr-name, attr-value)

所以DirPcDjg0725/settings.py模块中的变量就被赋作为类Settings的属性了,相当于读文件创建类属性名-属性值。

以后再看到莫名其妙,没有显式初始化的类属性,应该考虑是不是被setattr(self, attr-name, attr-value)初始化的属性,

另外参考了:django---加载INSTALLED_APPS的源码分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值