第二个命令是runserver
django项目的第一个命令
python manage.py startproject
django项目的第二个命令:
python manage.py runserver
本篇笔记在重点在于ArgumentParser
类,对runserver
本身的讨论较少。
关于 runserver Command
之前的操作,这里只说明一下大概:
manage.py
会导入DJANGO_SETTINGS_MODULE
settings
是一个懒加载,加载配置时,默认先加载global_settings.py
,再用DJANGO_SETTINGS_MODULE
中的配置覆盖,只读大写配置。connections
是一个懒加载的,代表对多个数据库连接的实例,connection
是对default
数据库的懒加载连接。Signal
类,可用于转发命令。apps
负责组织已安装的应用,populate
功能用于导入所有应用程序,django.setup()功能会调用populate
。- 通过
import_module("%s.management.commands.%s" % (app_name, name))
找到Command
在 startproject
时,BaseCommand
有一个遗留问题没有处理,便是 execute
中关于 "runserver"
的部分,这里拿出来分析一下:
# 即使代码损坏,也启动自动重新加载开发服务器。硬编码条件是一种`Code Smell`(代码异味),
# 但我们不能依赖命令类上的标志,因为我们还没有找到它。
if subcommand == "runserver" and "--noreload" not in self.argv:
# 当runserver,且需要reload时,走这一段儿代码
try:
autoreload.check_errors(django.setup)()
except Exception:
# 稍后将在自动重新加载器启动的子进程中引发异常。
# 通过加载空的应用程序列表来假装它没有发生。
apps.all_models = defaultdict(dict)
apps.app_configs = {}
apps.apps_ready = apps.models_ready = apps.ready = True
# 删除与内置运行服务器不兼容的选项(例如 contrib.staticfiles 的
# 运行服务器的选项)。此处的更改需要手动测试,如 #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)
这一段代码的关键是autoreload.check_errors
到底是何方神圣,它的来源是:from django.utils import autoreload
autoreload.check_errors
的代码如下:
def check_errors(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
global _exception
try:
fn(*args, **kwargs)
except Exception:
_exception = sys.exc_info()
et, ev, tb = _exception
if getattr(ev, "filename", None) is None:
filename = traceback.extract_tb(tb)[-1][0] # 从堆栈中的最后一项获取文件名
else:
filename = ev.filename
if filename not in _error_files:
_error_files.append(filename)
raise
return wrapper
这是一个非常典型的装饰器,这个装饰器看起来只是对fn
产生的Exception
做了一些处理,并抛出错误,这里使用autoreload.check_errors(django.setup)()
,和直接使用django.setup()
效果是差不多的。
如果django.setup()
出现错误,将在自动重新加载器启动的子进程中引发异常,这里暂时认为没有出错,并不加载应用。
这里暂时当作django.setup()
不会出错来分析程序,继续进行 runserver Command
类的分析。
题外话,如果django.setup()
出错了,十有八九是项目出现了错误,程序员应当考虑主动修复这个错误,而不是寄希望于django自行修复。
不难想象,runserver
会是一个复杂的命令,先来看看它的Command
头:
class Command(BaseCommand):
help = "Starts a lightweight web server for development."
# 每次重新加载服务器时都会显式调用验证
requires_system_checks = []
stealth_options = ("shutdown_message",)
suppressed_base_arguments = {"--verbosity", "--traceback"}
default_addr = "127.0.0.1"
default_addr_ipv6 = "::1"
default_port = "8000"
protocol = "http"
# django runserver默认使用WSGI服务器,django只是简单包装了一下
server_cls = WSGIServer
runserver
的 Command
的类,继承自BaseCommand
,execute
方法只是检查了一下no_color
,随后便使用了BaseCommand
的execute
,那就是没有中间商,直接看add_arguments
和handle
方法。
先来看看WSGIServer
:
from wsgiref import simple_server
class WSGIServer(simple_server.WSGIServer):
"""BaseHTTPServer that implements the Python WSGI protocol"""
request_queue_size = 10
def __init__(self, *args, ipv6=False, allow_reuse_address=True, **kwargs):
if ipv6:
self.address_family = socket.AF_INET6
self.allow_reuse_address = allow_reuse_address
super().__init__(*args, **kwargs)
def handle_error(self, request, client_address):
if is_broken_pipe_error():
logger.info("- Broken pipe from %s", client_address)
else:
super().handle_error(request, client_address)
从第三方包wsgiref
导入的simple_server
,这个wsgiref.simple_server.WSGIServer
是http.server.HTTPServer
的子类,看到http
这个包,就不用再往下走了,这是一个比较常见的python自带包,如果继续往下探究,就可以查到"万物起源"的socket.socket
django的WSGIServer
看起来只是对wsgiref.simple_server.WSGIServer
做了一个简单的包装。
在startproject
的笔记中分析过,Command
子类中最重要的两个方法是add_arguments
和handle
:
add_arguments
负责增加额外的参数:
def add_arguments(self, parser):
parser.add_argument(
"addrport", nargs="?", help="Optional port number, or ipaddr:port"
)
parser.add_argument(
"--ipv6",
"-6",
action="store_true",
dest="use_ipv6",
help="Tells Django to use an IPv6 address.",
)
parser.add_argument(
"--nothreading",
action="store_false",
dest="use_threading",
help="Tells Django to NOT use threading.",
)
parser.add_argument(
"--noreload",
action="store_false",
dest="use_reloader",
help="Tells Django to NOT use the auto-reloader.",
)
parser.add_argument(
"--skip-checks",
action="store_true",
help="Skip system checks.",
)
之前startproject
时,简单分析过parser
,对startproject
来说,parser
并没有展示出特别的效果,所以在startproject
时只进行了简单的描述。而runserver
时,具备了较多的功能,这里需要更详细的分析。
parser
是CommandParser
的实例,CommandParser
是对argparse.ArgumentParser
的一个简单包装,这里需要对argparse.ArgumentParser
的用法做简单了解,笔者推荐查看官方文档:https://docs.python.org/zh-cn/3/library/argparse.html
不过,作为源码分析的笔记,自然还是需要看看源码说了些什么,这里笔者比较关系的两个问题,一个是add_argument
做了什么,一个是parse_args
, 注意,这是 argparse.ArgumentParser
的源码,不是django
的。
由于第一个重点在add_argument
,这里只节选部分代码片段:
class ArgumentParser(_AttributeHolder, _ActionsContainer):
"""用于将命令行字符串解析为Python对象的对象。
关键字参数:
- prog -- 程序的名称(默认:sys.argv[0])
- usage -- 用法消息(默认:从参数自动生成)
- description -- 程序功能的描述
- epilog -- 参数描述后面的文本
- parents -- 其参数应复制到此的解析器
- formatter_class -- 用于打印帮助消息的 HelpFormatter 类
- prefix_chars -- 可选参数前缀的字符
- fromfile_prefix_chars -- 包含附加参数的文件前缀的字符
- argument_default -- 所有参数的默认值
- conflict_handler -- 指示如何处理冲突的字符串
- add_help -- 添加 -h/-help 选项
- allow_abbrev -- 允许明确缩写长选项
- exit_on_error -- 确定发生错误时 ArgumentParser 是否退出并显示错误信息
"""
def __init__(self,
prog=None,
usage=None,
description=None,
epilog=None,
parents=[],
formatter_class=HelpFormatter,
prefix_chars='-',
fromfile_prefix_chars=None,
argument_default=None,
conflict_handler='error',
add_help=True,
allow_abbrev=True,
exit_on_error=True):
superinit = super(ArgumentParser, self).__init__
superinit(description=description,
prefix_chars=prefix_chars,
argument_default=argument_default,
conflict_handler=conflict_handler)
这里以django.core.management.__init__.py
文件中,调用CommandParser
的流程,来分析superinit
传入了什么:
parser = CommandParser(
prog=self.prog_name, # 可以视作"manage.py"
usage="%(prog)s subcommand [options] [args]",
add_help=False,
allow_abbrev=False,
)
class CommandParser(ArgumentParser):
def __init__(
self, *, missing_args_message=None, called_from_command_line=None, **kwargs
):
self.missing_args_message = missing_args_message
self.called_from_command_line = called_from_command_line
super().__init__(**kwargs)
可以看出,superinit
需要的几个参数,与调用CommandParser
的传入参数无关,故实际效果为:
superinit(description=description, # None
prefix_chars=prefix_chars, # '-'
argument_default=argument_default, # None
conflict_handler=conflict_handler) # 'error'
ArgumentParser
的两个父类,只有_ActionsContainer
有__init__
方法,add_argument
这个方法也是由此类来定义的。
先看看_ActionsContainer
的__init__
方法:
import re as _re
from gettext import gettext as _, ngettext
class _ActionsContainer(object):
def __init__(self, description, prefix_chars,
argument_default, conflict_handler):
super(_ActionsContainer, self).__init__()
self.description = description # None
self.argument_default = argument_default # None
self.prefix_chars = prefix_chars # '-'
self.conflict_handler = conflict_handler # 'error'
# 设置注册表
self._registries = {}
# 注册actions
self.register('action', None, _StoreAction)
self.register('action', 'store', _StoreAction)
self.register('action', 'store_const', _StoreConstAction)
self.register('action', 'store_true', _StoreTrueAction)
self.register('action', 'store_false', _StoreFalseAction)
self.register('action', 'append', _AppendAction)
self.register('action', 'append_const', _AppendConstAction)
self.register('action', 'count', _CountAction)
self.register('action', 'help', _HelpAction)
self.register('action', 'version', _VersionAction)
self.register('action', 'parsers', _SubParsersAction)
self.register('action', 'extend', _ExtendAction)
# 如果冲突处理程序无效,则引发异常
# 寻找 self 的 _handle_conflict_error 方法,没有该方法就报错
self._get_handler()
# action存储
self._actions = []
self._option_string_actions = {}
# groups
self._action_groups = []
self._mutually_exclusive_groups = []
# 默认存储
self._defaults = {}
# 确定“选项”是否看起来像负数
self._negative_number_matcher = _re.compile(r'^-\d+$|^-\d*\.\d+$')
# 是否有任何看起来像负数的选项 -- 使用列表以便可以共享和编辑
self._has_negative_number_optionals = []
def register(self, registry_name, value, object): # 注册methods
registry = self._registries.setdefault(registry_name, {})
registry[value] = object
def _get_handler(self): # 从冲突处理程序字符串确定函数
handler_func_name = '_handle_conflict_%s' % self.conflict_handler
try:
return getattr(self, handler_func_name)
except AttributeError:
msg = _('invalid conflict_resolution value: %r')
raise ValueError(msg % self.conflict_handler)
def _handle_conflict_error(self, action, conflicting_actions):
message = ngettext('conflicting option string: %s',
'conflicting option strings: %s',
len(conflicting_actions))
conflict_string = ', '.join(
[option_string for option_string, action in conflicting_actions])
raise ArgumentError(action, message % conflict_string)
分析_ActionsContainer
的__init__
操作,主要是把一堆诸如_StoreAction
的类,以类似于’store’的名字,注册到self._registries["action"]
这个字典中,类似于:
self._registries = {
'action': {
None: "<class '_StoreAction'>",
'store': "<class '_StoreAction'>",
'store_const': "<class '_StoreConstAction'>",
'store_true': "<class '_StoreTrueAction'>",
'store_false': "<class '_StoreFalseAction'>",
# .... 此处省略其它的注册类
}
}
接下来可以研究_ActionsContainer
的add_argument
方法了:
def add_argument(self, *args, **kwargs):
"""
add_argument(dest, ..., name=value, ...)
add_argument(option_string, option_string, ..., name=value, ...)
"""
# 如果没有提供位置参数或者只提供了一个并且它看起来不像选项字符串,则解析位置参数
chars = self.prefix_chars # '-'
if not args or len(args) == 1 and args[0][0] not in chars:
# len(args)==1 且 args[0]不以"-"开头时,dest 为 args[0]
if args and 'dest' in kwargs: # 不能重复定义 dest
raise ValueError('dest supplied twice for positional argument')
kwargs = self._get_positional_kwargs(*args, **kwargs)
else: # 否则,我们将添加一个可选参数
kwargs = self._get_optional_kwargs(*args, **kwargs)
# 如果没有提供默认值,则使用解析器级别的默认值
if 'default' not in kwargs:
dest = kwargs['dest']
if dest in self._defaults:
kwargs['default'] = self._defaults[dest]
elif self.argument_default is not None:
kwargs['default'] = self.argument_default
# 创建操作对象,并将其添加到解析器
# 以kwargs中action="store_true"为例,action_class为_StoreTrueAction
action_class = self._pop_action_class(kwargs)
if not callable(action_class):
raise ValueError('unknown action "%s"' % (action_class,))
action = action_class(**kwargs)
# 如果操作类型不可调用,则会引发错误
type_func = self._registry_get('type', action.type, action.type)
if not callable(type_func):
raise ValueError('%r is not callable' % (type_func,))
if type_func is FileType:
raise ValueError('%r is a FileType class object, instance of it'
' must be passed' % (type_func,))
# 如果元变量与类型不匹配,则会引发错误
if hasattr(self, "_get_formatter"):
try:
self._get_formatter()._format_args(action, None)
except TypeError:
raise ValueError("length of metavar tuple does not match nargs")
return self._add_action(action)
def _get_positional_kwargs(self, dest, **kwargs):
if 'required' in kwargs: # 位置参数不能设定'required'(必需)属性
msg = _("'required' is an invalid argument for positionals")
raise TypeError(msg)
# 如果始终需要至少一个位置参数,则将位置参数标记为必需
# OPTIONAL = '?' ZERO_OR_MORE = '*'
if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]:
kwargs['required'] = True
if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs:
kwargs['required'] = True
# 返回不带选项字符串的关键字参数
return dict(kwargs, dest=dest, option_strings=[])
def _get_optional_kwargs(self, *args, **kwargs):
# 确定短选项字符串和长选项字符串
option_strings = []
long_option_strings = []
for option_string in args:
# 不以适当前缀开头的字符串将抛出错误
if not option_string[0] in self.prefix_chars:
args = {'option': option_string,
'prefix_chars': self.prefix_chars}
msg = _('invalid option string %(option)r: '
'must start with a character %(prefix_chars)r')
raise ValueError(msg % args)
# 以两个前缀字符开头的字符串是长选项
option_strings.append(option_string)
if len(option_string) > 1 and option_string[1] in self.prefix_chars:
long_option_strings.append(option_string)
# 推断dest,'--foo-bar' -> 'foo_bar' 和 '-x' -> 'x'
dest = kwargs.pop('dest', None)
if dest is None: # 如果没有dest,则进行推断
if long_option_strings:
dest_option_string = long_option_strings[0]
else:
dest_option_string = option_strings[0]
dest = dest_option_string.lstrip(self.prefix_chars)
if not dest:
msg = _('dest= is required for options like %r')
raise ValueError(msg % option_string)
dest = dest.replace('-', '_') # 'foo-bar' -> 'foo_bar'
# 返回更新后的关键字参数
return dict(kwargs, dest=dest, option_strings=option_strings)
def _pop_action_class(self, kwargs, default=None):
action = kwargs.pop('action', default)
return self._registry_get('action', action, action)
def _registry_get(self, registry_name, value, default=None):
return self._registries[registry_name].get(value, default)
def _add_action(self, action):
self._check_conflict(action) # 解决任何冲突
self._actions.append(action) # 添加到操作列表
action.container = self
# 通过具有的任何选项字符串对操作进行索引
for option_string in action.option_strings:
self._option_string_actions[option_string] = action
# 如果任何选项字符串看起来像负数,则设置标志
for option_string in action.option_strings:
if self._negative_number_matcher.match(option_string):
if not self._has_negative_number_optionals:
self._has_negative_number_optionals.append(True)
# 返回创建的action
return action
def _check_conflict(self, action):
confl_optionals = [] # 查找与该选项冲突的所有选项
for option_string in action.option_strings:
if option_string in self._option_string_actions:
confl_optional = self._option_string_actions[option_string]
confl_optionals.append((option_string, confl_optional))
if confl_optionals: # 解决任何冲突
conflict_handler = self._get_handler()
# 如果有冲突,默认的conflict_handler是直接raise error
conflict_handler(action, confl_optionals)
以_StoreTrueAction
为例,研究action
的属性:
class _StoreTrueAction(_StoreConstAction):
def __init__(self, option_strings, dest, default=False,
required=False, help=None):
super(_StoreTrueAction, self).__init__(
option_strings=option_strings,
dest=dest,
const=True,
default=default, # False 默认为False
required=required, # False 不是必须要传入的
help=help)
需要再向前查看其父类_StoreConstAction
:
class _StoreConstAction(Action):
def __init__(self, option_strings, dest, const, default=None,
required=False, help=None, metavar=None):
super(_StoreConstAction, self).__init__(
option_strings=option_strings,
dest=dest,
nargs=0,
const=const, # True
default=default, # False
required=required, # False
help=help)
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, self.const)
这个_StoreConstAction
具备了__call__
功能,或者重写了父类的__call__
功能,但__init__
需要查看父类Action
的内容:
class Action(_AttributeHolder):
"""
有关如何将命令行字符串转换为 Python 对象的信息。
ArgumentParser 使用 Action 对象来表示从命令行中的一个或多个字符串解析单个参数所需的信息。
Action 构造函数的关键字参数也是 Action 实例的所有属性。
关键字参数:
- option_strings -- 应与此操作关联的命令行选项字符串列表。
- dest -- 保存所创建对象的属性名称
- nargs -- 应该使用的命令行参数的数量。 默认情况下,将使用一个参数并生成一个值。
其他值包括:
- N(整数)使用 N 个参数(并生成一个列表)
- '?' 消耗零个或一个参数
- '*' 消耗零个或多个参数(并生成一个列表)
- '+' 使用一个或多个参数(并生成一个列表)
请注意,默认值和 nargs=1 之间的区别在于,使用默认值时,将生成单个
值,而使用 nargs=1 时,将生成包含单个值的列表。
- const -- 如果指定了选项并且该选项使用不带值的操作,则要生成的值。
- default -- 如果未指定选项则生成的值。
- type -- 接受单个字符串参数并返回转换后的值的可调用函数。 标准 Python 类型 str、
int、float 和 complex 是此类可调用项的有用示例。 如果没有,则使用 str。
- choices -- 应该允许的值的容器。如果不是 None,则在将命令行参数转换为适当的
类型后,如果它不是此集合的成员,则会引发异常。
- required -- 如果操作必须始终在命令行中指定,则为True。这仅对可选命令行参数有意义。
- help -- 描述参数的帮助字符串。
- metavar -- 用于带有帮助字符串的选项参数的名称。如果没有,则“dest”值将用作名称。
"""
def __init__(self, option_strings, dest, nargs=None,
const=None, default=None, type=None, choices=None,
required=False, help=None, metavar=None):
self.option_strings = option_strings
self.dest = dest
self.nargs = nargs
self.const = const # True
self.default = default # False
self.type = type
self.choices = choices
self.required = required # False
self.help = help
self.metavar = metavar
def _get_kwargs(self): # 将属性和值以元组形式返回
names = ['option_strings', 'dest', 'nargs', 'const', 'default',
'type', 'choices', 'required', 'help', 'metavar']
return [(name, getattr(self, name)) for name in names]
def format_usage(self):
return self.option_strings[0]
def __call__(self, parser, namespace, values, option_string=None):
raise NotImplementedError(_('.__call__() not defined'))
这个Action
类的__call__
会直接报错,也就是说,此方法是需要子类实现的,这个操作和模板模式是相似的。
这里其实已经可以不用继续看_AttributeHolder
了,并没有需要关注的功能了:
class _AttributeHolder(object):
"""提供 __repr__ 的抽象基类。
__repr__ 方法返回以下格式的字符串:
类名(attr=名称, attr=名称, ...)
这些属性由类级属性“_kwarg_names”确定,或者通过检查实例 __dict__ 来确定。
"""
def __repr__(self):
type_name = type(self).__name__
arg_strings = []
star_args = {}
for arg in self._get_args():
arg_strings.append(repr(arg))
for name, value in self._get_kwargs():
if name.isidentifier():
arg_strings.append('%s=%r' % (name, value))
else:
star_args[name] = value
if star_args:
arg_strings.append('**%s' % repr(star_args))
return '%s(%s)' % (type_name, ', '.join(arg_strings))
def _get_kwargs(self):
return list(self.__dict__.items())
def _get_args(self):
return []
当store_true的情况下,如果没有额外的传入,则其中有三个重要的参数如下:
self.const = const # True
self.default = default # False 默认值为False
self.required = required # False 默认并不是必须的
default
可以认为是action
的默认值
,required
为是否必须 传入
至此,add_arguments
已经分析完毕,这里结合代码看下实际效果。
分析add_arguments
中的参数addrport
:
parser.add_argument(
"addrport", nargs="?", help="Optional port number, or ipaddr:port"
)
这个addrport
会识别为位置参数,走_get_positional_kwargs
这条路线,nargs="?"
表示可有可无。
检测出来的Action
为:
_StoreAction(
option_strings=[],
dest='addrport',
nargs='?',
required=False, # 不是必须的
help='Optional port number, or ipaddr:port',
const=None,
default=None,
type=None,
choices=None,
metavar=None
)
分析add_arguments
中的参数--ipv6
:
parser.add_argument(
"--ipv6",
"-6",
action="store_true",
dest="use_ipv6",
help="Tells Django to use an IPv6 address.",
)
分析出来的Action
为:
_StoreTrueAction(
option_strings=['--ipv6', '-6'], # 即'--ipv6'和'-6'的效果一样
dest='use_ipv6',
nargs=0,
const=True,
default=False,
required=False,
help='Tells Django to use an IPv6 address.',
type=None,
choices=None,
metavar=None
)
这里剩下的另一个问题,parse_known_args
怎么工作:
def parse_known_args(self, args=None, namespace=None):
if args is None: # args 默认为系统参数
args = _sys.argv[1:]
else: # 确保 args 是可变的
args = list(args)
if namespace is None: # 从解析器默认值构建的默认命名空间
namespace = Namespace()
# 添加任何不存在的操作默认值
for action in self._actions:
# SUPPRESS = '==SUPPRESS=='
if action.dest is not SUPPRESS:
if not hasattr(namespace, action.dest):
if action.default is not SUPPRESS:
setattr(namespace, action.dest, action.default)
# 添加任何不存在的解析器默认值
for dest in self._defaults:
if not hasattr(namespace, dest):
setattr(namespace, dest, self._defaults[dest])
# 解析参数并在有任何错误时退出
if self.exit_on_error:
try:
namespace, args = self._parse_known_args(args, namespace)
except ArgumentError:
err = _sys.exc_info()[1]
self.error(str(err))
else:
namespace, args = self._parse_known_args(args, namespace)
if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR):
args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR))
delattr(namespace, _UNRECOGNIZED_ARGS_ATTR)
return namespace, args
这里需要先关心一下,self._actions
是什么:
[_HelpAction(option_strings=['-h', '--help'], dest='help', nargs=0, const=None, default='==SUPPRESS==', type=None,
choices=None, required=False, help='show this help message and exit', metavar=None),
_VersionAction(option_strings=['--version'], dest='version', nargs=0, const=None, default='==SUPPRESS==', type=None,
choices=None, required=False, help="Show program's version number and exit.", metavar=None),
_StoreAction(option_strings=['-v', '--verbosity'], dest='verbosity', nargs=None, const=None, default=1,
type="<class 'int'>", choices=[0, 1, 2, 3], required=False, help='==SUPPRESS==', metavar=None),
_StoreAction(option_strings=['--settings'], dest='settings', nargs=None, const=None, default=None, type=None,
choices=None, required=False, metavar=None,
help='The Python path to a settings module, e.g. "myproject.settings.main". '
'If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.'),
_StoreAction(option_strings=['--pythonpath'], dest='pythonpath', nargs=None, const=None, default=None, type=None,
choices=None, required=False,
help='A directory to add to the Python path, e.g. "/home/djangoprojects/myproject".', metavar=None),
_StoreTrueAction(option_strings=['--traceback'], dest='traceback', nargs=0, const=True, default=False, type=None,
choices=None, required=False, help='==SUPPRESS==', metavar=None),
_StoreTrueAction(option_strings=['--no-color'], dest='no_color', nargs=0, const=True, default=False, type=None,
choices=None, required=False, help="Don't colorize the command output.", metavar=None),
_StoreTrueAction(option_strings=['--force-color'], dest='force_color', nargs=0, const=True, default=False, type=None,
choices=None, required=False, help='Force colorization of the command output.', metavar=None),
_StoreAction(option_strings=[], dest='addrport', nargs='?', const=None, default=None, type=None, choices=None,
required=False, help='Optional port number, or ipaddr:port', metavar=None),
_StoreTrueAction(option_strings=['--ipv6', '-6'], dest='use_ipv6', nargs=0, const=True, default=False, type=None,
choices=None, required=False, help='Tells Django to use an IPv6 address.', metavar=None),
_StoreFalseAction(option_strings=['--nothreading'], dest='use_threading', nargs=0, const=False, default=True,
type=None, choices=None, required=False, help='Tells Django to NOT use threading.', metavar=None),
_StoreFalseAction(option_strings=['--noreload'], dest='use_reloader', nargs=0, const=False, default=True, type=None,
choices=None, required=False, help='Tells Django to NOT use the auto-reloader.', metavar=None),
_StoreTrueAction(option_strings=['--skip-checks'], dest='skip_checks', nargs=0, const=True, default=False, type=None,
choices=None, required=False, help='Skip system checks.', metavar=None),
_StoreFalseAction(option_strings=['--nostatic'], dest='use_static_handler', nargs=0, const=False, default=True,
type=None, choices=None, required=False,
help='Tells Django to NOT automatically serve static files at STATIC_URL.', metavar=None),
_StoreTrueAction(option_strings=['--insecure'], dest='insecure_serving', nargs=0, const=True, default=False, type=None,
choices=None, required=False, help='Allows serving static files even if DEBUG is False.', metavar=None)
]
除了Command
中add_arguments
添加的几个Action
外,还有BaseCommand
的create_parser
中给出的几个参数,比如"--version"
等,这些参数都将在namespace
中使用,即setattr(namespace, action.dest, action.default)
这里使用。
_parse_known_args
这里将default数值修改为真正的数值,这个操作在_parse_known_args
的take_action
方法里,通过各类Action
的__call__
来实现,并在其中做了相当多的检查,例如required
(强制需要
)的属性是否定义,这个函数相当的复杂,但核心的功能并不难理解,这里以'addrport'
使用的类_StoreAction
为例子,看一下__call__
的操作:
class _StoreAction(Action):
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, values)
很好理解,这就是简单的把namespace
的self.dest
(这里是addrport
)属性改为values
,其它的Action
各有不同,但也是大差不差,比如_StoreTrueAction
,如果没有该参数(比如'--ipv6'
),则默认是False
,如果有这个参数,就将namespace
的self.dest
设置为self.const
,这里的self.const
是True
class _StoreTrueAction(_StoreConstAction):
def __init__(self,
option_strings,
dest,
default=False,
required=False,
help=None):
super(_StoreTrueAction, self).__init__(
option_strings=option_strings,
dest=dest,
const=True, # const是true
default=default, # 默认是False
required=required,
help=help)
class _StoreConstAction(Action):
def __call__(self, parser, namespace, values, option_string=None):
setattr(namespace, self.dest, self.const)
分析一个实际例子:django_test.manage.py runserver 127.0.0.1:8000
,则解析出的namespace
中存在属性addrport
的值为'127.0.0.1:8000'
;另一个参数'--ipv6'
不存在,解析出的namespace
中存在属性use_ipv6
的值为False
,其它参数可以此类推。
这里关于各种Action
很好的体现出了编程过程中一个原则:功能单一。也就是封装过程中,不要赋予一个类过多的功能,便于后续维护,实现低耦合。这一系列的Action
类,其实只用Action
类这一种类,也是可以实现的,比如通过添加一些额外的属性,这样是可以减少类,但会增加耦合程度。反过来说,这种功能单一
的设计原则,也会导致需要实现一大堆的类,对于熟悉这套程序的人来说是有利的,对于研究这段程序的新手就不太友好了,至少笔者为了弄明白这套东西,把所有的Action
类翻了一遍,结果是功能大差不差,干脆就不在笔记里一一解析了。
这里看完了 Command
的 add_arguments
,然后是看看 handle
做些什么:
def handle(self, *args, **options):
if not settings.DEBUG and not settings.ALLOWED_HOSTS:
raise CommandError("You must set settings.ALLOWED_HOSTS if DEBUG is False.")
# 判定是否使用ipv6,现阶段一般还是使用ipv4较多
self.use_ipv6 = options["use_ipv6"]
if self.use_ipv6 and not socket.has_ipv6:
raise CommandError("Your Python does not support IPv6.")
self._raw_ipv6 = False
if not options["addrport"]:
self.addr = ""
self.port = self.default_port
else:
m = re.match(naiveip_re, options["addrport"])
if m is None:
raise CommandError(
'"%s" is not a valid port number '
"or address:port pair." % options["addrport"]
)
self.addr, _ipv4, _ipv6, _fqdn, self.port = m.groups()
if not self.port.isdigit():
raise CommandError("%r is not a valid port number." % self.port)
if self.addr:
if _ipv6:
self.addr = self.addr[1:-1]
self.use_ipv6 = True
self._raw_ipv6 = True
elif self.use_ipv6 and not _fqdn:
raise CommandError('"%s" is not a valid IPv6 address.' % self.addr)
if not self.addr:
self.addr = self.default_addr_ipv6 if self.use_ipv6 else self.default_addr
self._raw_ipv6 = self.use_ipv6
self.run(**options)
def run(self, **options):
"""运行服务器,如果需要的话使用自动重新加载器"""
use_reloader = options["use_reloader"]
if use_reloader:
autoreload.run_with_reloader(self.inner_run, **options)
else:
self.inner_run(None, **options)
handle
看起来就是分析了一下ip地址、是否为ipv6,还是看看 run
的效果:
默认情况下,run
会使用 autoreload.run_with_reloader
进行运转,关于为什么默认是use_reload
,是因为这个action
:
_StoreFalseAction(option_strings=['--noreload'], dest='use_reloader', nargs=0, const=False, default=True, type=None,
choices=None, required=False, help='Tells Django to NOT use the auto-reloader.', metavar=None),
这里先看看autoreload.run_with_reloader
做了什么:
def run_with_reloader(main_func, *args, **kwargs):
signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
try:
if os.environ.get(DJANGO_AUTORELOAD_ENV) == "true":
reloader = get_reloader()
logger.info(
"Watching for file changes with %s", reloader.__class__.__name__
)
start_django(reloader, main_func, *args, **kwargs)
else:
exit_code = restart_with_reloader()
sys.exit(exit_code)
except KeyboardInterrupt:
pass
首先是signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
,这里的操作就是将signal.SIGTERM
(15,表示正常终止)信号绑定lambda *args: sys.exit(0)
这个匿名函数,这里可以解释为收到signal.SIGTERM
信号,就让进程终止。
然后是if os.environ.get(DJANGO_AUTORELOAD_ENV) == "true":
这里,笔者翻了一遍代码,发现这个DJANGO_AUTORELOAD_ENV
只在两个地方用到过,一个是在文件初始化的时候,一个是在restart_with_reloader
:
DJANGO_AUTORELOAD_ENV = "RUN_MAIN"
def restart_with_reloader():
new_environ = {**os.environ, DJANGO_AUTORELOAD_ENV: "true"}
args = get_child_arguments()
while True:
p = subprocess.run(args, env=new_environ, close_fds=False)
if p.returncode != 3:
return p.returncode
这里分析一下DJANGO_AUTORELOAD_ENV
的作用,它在一开始并没有出现在os.environ
中,这样就会走exit_code = restart_with_reloader()
这个流程,然后就会走到restart_with_reloader
方法中的p = subprocess.run(args, env=new_environ, close_fds=False)
这里,以python manage.py runserver 127.0.0.1:8000
为例,这里的args
的值可以视作['python.exe', 'manage.py', 'runserver', '127.0.0.1:8000']
,这个args
由get_child_arguments
获得,这个方法就是一个简单的判断,故不对这个方法进行分析。
subprocess.run
通过调用子进程来运行args
,子进程
这里的环境变成了new_environ
,也就走到了
if os.environ.get(DJANGO_AUTORELOAD_ENV) == "true":
reloader = get_reloader()
logger.info(
"Watching for file changes with %s", reloader.__class__.__name__
)
start_django(reloader, main_func, *args, **kwargs)
这个判断中。
这里回头看run_with_reloader
的流程,就是用主进程打开子进程来进行实际的处理,而主进程只负责监管子进程。
接下来需要看看子进程怎么走,首先是获得reloader
,即reloader = get_reloader()
:
def get_reloader():
"""返回最适合此环境的重新加载器"""
try:
WatchmanReloader.check_availability()
except WatchmanUnavailable:
return StatReloader()
return WatchmanReloader()
重载器默认5秒进行一次重载,这里的WatchmanReloader
以pywatchman
为基础进行编写,不过目前有些意见认为pywatchman
已经出现了过时的情况,应当重新选定一个更好的watchman
.
如果WatchmanReloader
出错(比如没有安装pywatchman
),则选择StatReloader
.
StatReloader
的花费,按一些论坛上的说法,是比WatchmanReloader
要大的(笔者这里也不确定,笔者并未进行实测)
,但作为更常见的方案,这里值得分析一下:
class StatReloader(BaseReloader):
SLEEP_TIME = 1 # 每秒检查一次更改
def tick(self):
mtimes = {}
while True:
for filepath, mtime in self.snapshot_files():
old_time = mtimes.get(filepath)
mtimes[filepath] = mtime
if old_time is None:
logger.debug("File %s first seen with mtime %s", filepath, mtime)
continue
elif mtime > old_time:
logger.debug(
"File %s previous mtime: %s, current mtime: %s",
filepath, old_time, mtime)
self.notify_file_changed(filepath) # 提醒文件发生了变更
time.sleep(self.SLEEP_TIME)
yield
def snapshot_files(self):
# 如果全局重叠,watched_files 可能会产生重复的路径。
seen_files = set()
for file in self.watched_files():
if file in seen_files:
continue
try:
mtime = file.stat().st_mtime
except OSError:
# 当文件不存在时抛出此错误。
continue
seen_files.add(file)
yield file, mtime
@classmethod
def check_availability(cls):
return True
这里能够看出,StatReloader
的tick
在调用时,会每秒检查一下,如果文件变更,则通过self.notify_file_changed
提醒文件发生了变更。
BaseReloader
这个类的功能是比较多的,还是先看看start_django
会调用到哪个功能,再继续分析:
def start_django(reloader, main_func, *args, **kwargs):
ensure_echo_on()
main_func = check_errors(main_func)
django_main_thread = threading.Thread(
target=main_func, args=args, kwargs=kwargs, name="django-main-thread"
)
django_main_thread.daemon = True
django_main_thread.start()
while not reloader.should_stop:
reloader.run(django_main_thread)
查看ensure_echo_on
:
def ensure_echo_on():
"""
确保启用回显模式。某些工具(例如 PDB)会禁
用它,这会导致重新加载后出现可用性问题。
"""
if not termios or not sys.stdin.isatty():
return
pass # ...省略这段代码
termios
这里并没有使用,所以也就直接return
了,直接往后看:
main_func = check_errors(main_func)
def check_errors(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
global _exception
try:
fn(*args, **kwargs)
except Exception:
_exception = sys.exc_info()
et, ev, tb = _exception
if getattr(ev, "filename", None) is None:
filename = traceback.extract_tb(tb)[-1][0]
else:
filename = ev.filename
if filename not in _error_files:
_error_files.append(filename)
raise
return wrapper
check_errors
这里就是一个简单的装饰器,将错误做了一些处理,可以跳过。
这之后便是创建一个主线程并start()
,如果reloader
并不需要停止,则使用reloader.run(django_main_thread)
需要查看一下run
函数的功能,run
函数不在StatReloader
中,则说明处于父类里,这里找到BaseReloader
的功能:
class BaseReloader:
def __init__(self):
self.extra_files = set()
self.directory_globs = defaultdict(set)
self._stop_condition = threading.Event()
def run(self, django_main_thread):
logger.debug("Waiting for apps ready_event.")
self.wait_for_apps_ready(apps, django_main_thread)
from django.urls import get_resolver
# 通过访问 urlconf_module 属性,防止重新加载器启动时未加载 URL 模块的竞争情况。
try:
get_resolver().urlconf_module
except Exception:
# 加载 urlconf 可能会导致开发过程中出现错误。如果发生这种情况,忽略错误并继续。
pass
logger.debug("Apps ready_event triggered. Sending autoreload_started signal.")
autoreload_started.send(sender=self)
self.run_loop()
def run_loop(self):
ticker = self.tick()
while not self.should_stop:
try:
next(ticker)
except StopIteration:
break
self.stop()
主要的功能在于run
,这里有必要看一下wait_for_apps_ready
:
def wait_for_apps_ready(self, app_reg, django_main_thread):
"""
等待 Django 报告应用程序已加载。如果给定线程在应用程序准备就绪之前终止,则会引发
SyntaxError 或其他不可恢复的错误。在这种情况下,请停止等待 apps_ready 事件并继续处理。
如果线程处于活动状态并且已触发就绪事件,则返回 True;如果线程在等待事件时终止,则返回 False。
"""
while django_main_thread.is_alive():
if app_reg.ready_event.wait(timeout=0.1):
return True
else:
logger.debug("Main Django thread has terminated before apps are ready.")
return False
这个app_reg
正是apps
,在startproject
的时候分析过App.populate
,正是在这里存在self.ready_event.set()
,也就是说,如果在0.1秒内完成了初始化,则返回True
,否则返回False
,这里算是把第一篇中的坑给填了,ready_event
用在了这里。
get_resolver().urlconf_module
这里加载了一下 settings.ROOT_URLCONF
,这个地方确实没看出实际效果。
run_loop
则是对self.tick()
的一个包装,但笔者有个疑问并未解决,那便是,这里为什么要使用一个生成器来设计tick()
,把tick()
和run_loop
合并为一个死循环呢?
笔者有一个猜测,这里可能是django自己实现了一个类似于async的流程,也就是利用生成器,实现同步无阻塞,从而在外面看起来就是异步的。
但是,asyncio还有一个调度系统呢,会随着操作系统的不同而发生变更,而这里两个循环,能实现无阻塞吗,这里令人疑惑,笔者也没有想到明确的答案。
这里结束对重载系统的探讨,回到runserver
的run
方法,重载器的功能总结一下,也就是每过一秒(StatReloader
)或五秒(WatchmanReloader
),检查一下是否需要重载,不论是有重载器还是没有,这里真正处理业务的,都是这个self.inner_run
功能:
class Command(BaseCommand):
# ...
def inner_run(self, *args, **options):
# 如果异常在 ManagementUtility.execute 中被静音以便在子进程中引发,请立即引发。
autoreload.raise_last_exception()
threading = options["use_threading"]
# “shutdown_message”是一个隐秘选项。
shutdown_message = options.get("shutdown_message", "")
if not options["skip_checks"]:
self.stdout.write("Performing system checks...\n\n")
self.check(display_num_errors=True)
# 需要在这里检查迁移,因此不能使用requires_migrations_check属性。
self.check_migrations()
try:
handler = self.get_handler(*args, **options)
run(
self.addr,
int(self.port),
handler,
ipv6=self.use_ipv6,
threading=threading,
on_bind=self.on_bind,
server_cls=self.server_cls,
)
except OSError as e:
# 使用有用的错误消息而不是丑陋的回溯
ERRORS = {
errno.EACCES: "You don't have permission to access that port.",
errno.EADDRINUSE: "That port is already in use.",
errno.EADDRNOTAVAIL: "That IP address can't be assigned to.",
}
try:
error_text = ERRORS[e.errno]
except KeyError:
error_text = e
self.stderr.write("Error: %s" % error_text)
# 需要使用操作系统退出,因为 sys.exit 在线程中不起作用
os._exit(1)
except KeyboardInterrupt:
if shutdown_message:
self.stdout.write(shutdown_message)
sys.exit(0)
分析inner_run
,这里的autoreload.raise_last_exception()
:
def raise_last_exception():
global _exception
if _exception is not None:
raise _exception[1]
如果autoreload
有错误,则抛出,看起来好像没啥用。
self.check
这里比较麻烦,直接看一下它的注释:
使用系统检查框架来验证整个 Django 项目。
对于任何严重消息(错误或严重错误),引发 CommandError。
如果只有轻微消息(如警告),请将它们打印到 stderr 并且不要引发异常。
check_migrations
则是检查迁移文件,如果磁盘上的迁移集与数据库中的迁移不匹配,则打印警告。
这里存在一个操作executor = MigrationExecutor(connections[DEFAULT_DB_ALIAS])
,调用了connections
中的DEFAULT_DB_ALIAS
,故在这里的时候,已经建立了与connections
中DEFAULT_DB_ALIAS
数据库的连接。
get_handler
的作用,最终是获得一个WSGIHandler
:
def get_handler(self, *args, **options):
"""返回运行器的默认 WSGI 处理程序"""
return get_internal_wsgi_application()
这里有必要对get_internal_wsgi_application()
进行查看:
def get_internal_wsgi_application():
from django.conf import settings
app_path = getattr(settings, "WSGI_APPLICATION")
if app_path is None:
return get_wsgi_application()
try:
return import_string(app_path)
except ImportError as err:
raise ImproperlyConfigured(
"WSGI application '%s' could not be loaded; "
"Error importing module." % app_path
) from err
这个"WSGI_APPLICATION"
在项目生成时,默认为:'项目名(例如django_test).wsgi.application'
默认生成的wsgi.py文件内容为:
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', '项目名(例如django_test).settings')
application = get_wsgi_application()
所以默认情况下,绕一圈后,还是同一个WSGIHandler
,不同之处在于又重新导入了一次'DJANGO_SETTINGS_MODULE'
.
由于这个WSGIHandler
是相当复杂的,还是先看看哪里用到了:
def run(
addr,
port,
wsgi_handler,
ipv6=False,
threading=False,
on_bind=None,
server_cls=WSGIServer,
):
server_address = (addr, port)
if threading:
httpd_cls = type("WSGIServer", (socketserver.ThreadingMixIn, server_cls), {})
else:
httpd_cls = server_cls
httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
if on_bind is not None:
on_bind(getattr(httpd, "server_port", port))
if threading:
# ThreadingMixIn.daemon_threads 指示线程在突然关闭时的行为方式;
# 例如用户退出服务器或自动重新加载器重新启动。 True 意味着服务器
# 在退出之前不会等待线程终止。 这将使自动重新加载速度更快,并且
# 如果线程未正确终止,则无需手动终止服务器。
httpd.daemon_threads = True
httpd.set_app(wsgi_handler)
httpd.serve_forever()
httpd
正是WSGIServer
这个类,在笔记最开始的时候,就分析了WSGIServer
这个类,它本身是wsgiref.WSGIServer
类的简单包装,这个set_app
是wsgiref.WSGIServer
类的功能:
def set_app(self, application):
self.application = application
那么这里反过来看,WSGIHandler
类最需要关注__init__
方法:
class WSGIHandler(base.BaseHandler):
request_class = WSGIRequest
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.load_middleware()
def __call__(self, environ, start_response):
set_script_prefix(get_script_name(environ))
signals.request_started.send(sender=self.__class__, environ=environ)
request = self.request_class(environ)
response = self.get_response(request)
response._handler_class = self.__class__
status = "%d %s" % (response.status_code, response.reason_phrase)
response_headers = [
*response.items(),
*(("Set-Cookie", c.output(header="")) for c in response.cookies.values()),
]
start_response(status, response_headers)
if getattr(response, "file_to_stream", None) is not None and environ.get(
"wsgi.file_wrapper"
):
# 如果使用“wsgi.file_wrapper”,WSGI 服务器不会在响应上调用 .close,而是在文
# 件包装器上调用 .close。 修补它以使用response.close 来代替它负责关闭所有文件。
response.file_to_stream.close = response.close
response = environ["wsgi.file_wrapper"](
response.file_to_stream, response.block_size
)
return response
WSGIHandler
类__init__
方法,调用了父类的BaseHandler
方法,父类没有直接定义__init__
方法,故这里调用的是object
的__init__
方法,然后调用self.load_middleware()
,而WSGIHandler
类没有定义该方法,故调用BaseHandler
的load_middleware
方法:
class BaseHandler:
_view_middleware = None
_template_response_middleware = None
_exception_middleware = None
_middleware_chain = None
def load_middleware(self, is_async=False):
"""
从 settings.MIDDLEWARE 填充中间件列表。
必须在环境修复后调用(参见子类中的 __call__ ).
"""
self._view_middleware = []
self._template_response_middleware = []
self._exception_middleware = []
get_response = self._get_response_async if is_async else self._get_response
handler = convert_exception_to_response(get_response) # 将异常转换为响应
handler_is_async = is_async
for middleware_path in reversed(settings.MIDDLEWARE):
middleware = import_string(middleware_path)
middleware_can_sync = getattr(middleware, "sync_capable", True)
middleware_can_async = getattr(middleware, "async_capable", False)
if not middleware_can_sync and not middleware_can_async:
raise RuntimeError(
"Middleware %s must have at least one of "
"sync_capable/async_capable set to True." % middleware_path
)
elif not handler_is_async and middleware_can_sync:
middleware_is_async = False
else:
middleware_is_async = middleware_can_async
try:
# 如果需要,调整处理程序
adapted_handler = self.adapt_method_mode(
middleware_is_async,
handler,
handler_is_async,
debug=settings.DEBUG,
name="middleware %s" % middleware_path,
)
mw_instance = middleware(adapted_handler)
except MiddlewareNotUsed as exc:
if settings.DEBUG:
if str(exc):
logger.debug("MiddlewareNotUsed(%r): %s", middleware_path, exc)
else:
logger.debug("MiddlewareNotUsed: %r", middleware_path)
continue
else:
handler = adapted_handler
if mw_instance is None:
raise ImproperlyConfigured(
"Middleware factory %s returned None." % middleware_path
)
if hasattr(mw_instance, "process_view"):
self._view_middleware.insert(
0,
self.adapt_method_mode(is_async, mw_instance.process_view),
)
if hasattr(mw_instance, "process_template_response"):
self._template_response_middleware.append(
self.adapt_method_mode(
is_async, mw_instance.process_template_response
),
)
if hasattr(mw_instance, "process_exception"):
# 目前,异常处理堆栈仍然始终是同步的,因此请以这种方式进行调整。
self._exception_middleware.append(
self.adapt_method_mode(False, mw_instance.process_exception),
)
handler = convert_exception_to_response(mw_instance)
handler_is_async = middleware_is_async
# 如果需要,调整堆栈顶部。
handler = self.adapt_method_mode(is_async, handler, handler_is_async)
# 我们仅在初始化完成时分配给它,因为它用作初始化完成的标志。
self._middleware_chain = handler
这里不再对load_middleware
进行详细的分析,但是这里有一个很有意思的东西:
handler = convert_exception_to_response(get_response)
这个方法的源码节选:
def convert_exception_to_response(get_response):
"""
将给定的 get_response 可调用包装在异常到响应的转换中。 所有异常都将被转换。
所有已知的 4xx 异常(Http404、PermissionDenied、 MultiPartParserError、
SuspiciousOperation)将转换为适当的响应,所有其他异常将转换为 500 响应。
该装饰器会自动应用于所有中间件,以确保没有中间件泄漏异常,并且堆栈中
的下一个中间件可以依赖于获取响应而不是异常。
"""
if iscoroutinefunction(get_response):
@wraps(get_response)
async def inner(request):
try:
response = await get_response(request)
except Exception as exc:
response = await sync_to_async(
response_for_exception, thread_sensitive=False
)(request, exc)
return response
return inner
else:
@wraps(get_response)
def inner(request):
try:
response = get_response(request)
except Exception as exc:
response = response_for_exception(request, exc)
return response
return inner
这是一个很有趣的装饰器,但是可以发现,django
代码中很少直接使用@
来装饰一个功能,反而是handler = convert_exception_to_response(get_response)
这种方式使用装饰器更常见,这是一种更加灵活的,使用装饰器的方式,如果有必要,可以模仿这种使用方法。
至此,对于runserver
的大致分析完毕,总结一下runserver
过程中做过的事情,包括常规的manage.py
的工作,以及自己的特性工作。
其中,manage.py
的几个重要工作为:
- 设定环境变量
DJANGO_SETTINGS_MODULE
- 创造数据库懒连接,但不进行实际连接
- 组织已安装的应用
runserver
的特性工作包括:
- 重载器
- 创建简单的
WSGIServer
(这个服务器并不是高可用的,不建议线上使用) - 检查整个项目和迁移文件
- 与数据库进行实际连接
- 加载中间件(含错误处理,如404等)
与django无关的新知识点:
ArgumentParser
- 装饰器的非
@
用法