django启动流程源码解读

前言

这篇笔记主要是对django启动流程源码查阅,我用的django版本是2.1.8,在记录的时候我将部分代码进行了删减来更好的理解。写这个笔记主要是为了让自己更了解django,我的能力也有限,写的时候花了时间和精力,但如果有理解上不适或者错误的地方请指点。

启动流程

先看下manager.py

def main():

    # 将settings模块设置到环境变量中
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoStu.settings')
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    # 执行命令(sys.argv:执行命令语句)
    execute_from_command_line(sys.argv)

manager.py做了两件事情,第一是吧setting模块加载到环境变量中,第二就是执行命令。

下面我们来到execute_from_command_line 方法中看下它的作用是什么

execute_from_command_line方法

def execute_from_command_line(argv=None):
    """Run a ManagementUtility."""
    utility = ManagementUtility(argv)
    utility.execute()

从代码可以知道首先实例化了ManagementUtility(封装django-admin和manage.py实用程序的类),然后执行了execute。

ManagementUtility的实例化方法__init__.py

def __init__(self, argv=None):
    self.argv = argv or sys.argv[:]
    # self.prog_name:manager.py
    self.prog_name = os.path.basename(self.argv[0])
    # 判断是都是__main__.py
    if self.prog_name == '__main__.py':
        self.prog_name = 'python -m django'
    self.settings_exception = None

下面我们接着看看execute方法到底做了什么

execute方法

为了方便理解execute方法做了什么事情,我把部分代码去除掉,如果想要看具体的自己去查看源码吧

def execute(self):
    try:
        # 取得runserver参数
        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
    if settings.configured:
        # 对runserver进行处理
        if subcommand == 'runserver' and '--noreload' not in self.argv:
            autoreload.check_errors(django.setup)()
        else:
            django.setup()
    self.autocomplete()
    # subcommand命令的判断
    if subcommand == 'help':
        pass
    elif subcommand == 'version' or self.argv[1:] == ['--version']:
        pass
    elif self.argv[1:] in (['--help'], ['-h']):
        pass
    else:
        self.fetch_command(subcommand).run_from_argv(self.argv)

从上面代码我们可以看出,execute方法中首先对self.argv[1]是否是runserver进行处理,如果不是则赋值help,后面进行处理,处理代码见如下所示,判断subcommand 是否是 help ,是否是 version ,根据不同的情况展示不同的信息,如果都不是则走self.fetch_command(subcommand).run_from_argv(self.argv)。

判断self.argv[1]逻辑如下所示:

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])

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)

handle_default_options(默认处理)

再往下看就是对对--settings和--pythonpath进行了预处理,因为这个如果有问题会影响使用,并且后面对其进行默认处理。

def handle_default_options(options):
    """
    在此处包括所有命令应接受的所有默认选项,以便ManagementUtility可以在搜索用户命令之前对其进行处理。
    """
    if options.settings:
        os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
    if options.pythonpath:
        sys.path.insert(0, options.pythonpath)

runserver处理

# 对runserver进行处理
if subcommand == 'runserver' and '--noreload' not in self.argv:
    autoreload.check_errors(django.setup)()
else:
    django.setup()

再然后对runserver进行处理,如上面execute源码所示:第一个是会自动重装的路线,通过 autoreload.check_errors(django.setup)() 代理完成(通过闭包来捕捉了异常)。另一个路线是参数中有 --noreload 时,就用 django.setup() 来启动服务。

subcommand 进行处理

最后就是开始所说的对subcommand 进行处理,如果不是help或者version则进入 self.fetch_command(subcommand).run_from_argv(self.argv) 。这方法分两步,一步是获取执行命令所需要的类,其次是将命令参数作为参数传递给执行函数执行。

1- 我们先看下fetch_command代码,为了方便理解拿掉部分代码

def fetch_command(self, subcommand):
    """
    尝试获取给定的子命令,如果找不到,则使用从命令行调用的相应命令(通常为“ django-admin”或“ manage.py”)打印一条消息。
    """
    # 将字典映射命令名称返回到其回调应用程序。
    # {command_name:app_name} 例子:{'check': 'django.core', 'compilemessages': 'django.core',.....}
    commands = get_commands()
    try:
        app_name = commands[subcommand]
    except KeyError:
        # 退出告知用户
        sys.exit(1)
    if isinstance(app_name, BaseCommand):
        # 如果已经加载,则直接使用
        klass = app_name
    else:
        # load_command_class:自动装载
        klass = load_command_class(app_name, subcommand)
    return klass

fetch_command方法中 get_commands返回的是一个字典{command_name:app_name},get_commands 这部分作者是采用了python 的functools.lru_cache来实现缓存,不需要每次都重新加载,如下所示:

@functools.lru_cache(maxsize=None)
def get_commands():
    """
    将字典映射命令名称返回到其回调应用程序。

    """
    commands = {name: 'django.core' for name in find_commands(__path__[0])}

    if not settings.configured:
        return commands

    for app_config in reversed(list(apps.get_app_configs())):
        path = os.path.join(app_config.path, 'management')
        commands.update({name: app_config.name for name in find_commands(path)})

    return commands

如果app_name 已经加载了则直接使用,反之通过load_command_class来动态加载模块的:

def load_command_class(app_name, name):
    module = import_module('%s.management.commands.%s' % (app_name, name))
    return module.Command()

了解了fetch_command的代码,我们看看run_from_argv

2- run_from_argv代码解析

如执行 runserver 命令的模块就是 django.contrib.staticfiles.management.commands.runserver 返回该模块中定义的 Command 类的实例。获得实例后调用了 run_from_argv(self.argv) :

def run_from_argv(self, argv):
    self._called_from_command_line = True
    parser = self.create_parser(argv[0], argv[1])
    # Namespace(addrport=None, ...) 返回一个Namespace的实例
    options = parser.parse_args(argv[2:]) 
    cmd_options = vars(options) # 对象转成字典
    # 将位置参数移出选项以模仿旧版optparse
    args = cmd_options.pop('args', ())
    # 设置默认参数
    handle_default_options(options)     
    try:
        # 异常捕获包裹的execute
        self.execute(*args, **cmd_options) 
    except Exception as e:
        sys.exit(1)
    finally:
        connections.close_all()

 

推荐阅读

python缓存机制与functools.lru_cache

你可能感兴趣的:(django,python,python,django)