这篇笔记主要是对django启动流程源码查阅,我用的django版本是2.1.8,在记录的时候我将部分代码进行了删减来更好的理解。写这个笔记主要是为了让自己更了解django,我的能力也有限,写的时候花了时间和精力,但如果有理解上不适或者错误的地方请指点。
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方法做了什么事情,我把部分代码去除掉,如果想要看具体的自己去查看源码吧
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)
再往下看就是对对--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进行处理
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 进行处理,如果不是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