想跟一下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)初始化的属性,