日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

django源码简析——后台程序入口

發布時間:2023/12/9 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 django源码简析——后台程序入口 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
django源碼簡析——后臺程序入口

  這一年一直在用云筆記,平時記錄一些tips或者問題很方便,所以也就不再用博客進行記錄,還是想把最近學習到的一些東西和大家作以分享,也能夠對自己做一個總結。工作中主要基于django框架,進行項目的開發,我是主要做后臺相關比較多一些,熟悉django的同學知道,django的后臺進程通常通過下面這種方式運行:

python manage.py app [options]

  我們假設當前的項目名為myproject,這里app表示要運行的app名稱,具體為django項目中module/management/commands中定義的進程文件名,options表示一些可選的參數。以python manage app為例,看下它的運行原理。manage.py是在項目創建之后,自動生成的一個py文件,它的定義如下:

if __name__ == "__main__":os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")from django.core.management import execute_from_command_lineexecute_from_command_line(sys.argv)

  execute_from_command_line 方法用于讀取命令行參數,并執行相應的app程序代碼:

def execute_from_command_line(argv=None):"""A simple method that runs a ManagementUtility."""utility = ManagementUtility(argv)utility.execute()

  從這里可以看出,實際上app程序是通過 ManagementUtility.execute() 方法來執行的。execute方法定義在django.core.manage.__init__.py中:

def execute(self):try:subcommand = self.argv[1]except IndexError:subcommand = 'help' # Display help if no arguments were given. parser = CommandParser(None, usage="%(prog)s subcommand [options] [args]", add_help=False)parser.add_argument('--settings')parser.add_argument('--pythonpath')parser.add_argument('args', nargs='*') # catch-alltry:options, args = parser.parse_known_args(self.argv[2:])handle_default_options(options)except CommandError:pass # Ignore any option errors at this point. no_settings_commands = ['help', 'version', '--help', '--version', '-h','compilemessages', 'makemessages','startapp', 'startproject',]try:settings.INSTALLED_APPSexcept ImproperlyConfigured as exc:self.settings_exception = excif subcommand in no_settings_commands:settings.configure()if settings.configured:if subcommand == 'runserver' and '--noreload' not in self.argv:try:autoreload.check_errors(django.setup)()except Exception:passelse:django.setup()self.autocomplete()if subcommand == 'help':if '--commands' in args:sys.stdout.write(self.main_help_text(commands_only=True) + '\n')elif len(options.args) < 1: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)

?  我們來分解一下這段程序,subcommnad是python manage.py后的參數,即子程序名,argv[0]表示manage.py。這里如果沒有指定,那么子程序默認為help。接著通過CommandParser來解析隨后的參數,app子程序名之后的參數,這里我們默認沒有其他參數。接著在try語句中執行 settings.INSTALLED_APPS,這句乍看上去很是不解,沒有賦值,沒有輸出,注意settings是django.conf.__init__.py中定義的一個LazySettings對象,LazySettings繼承自LazyObject類,它重寫了__getattr__和__setattr__方法,那么在調用settings.INSTALLED_APPS時,會通過其自定義的__getattr__方法實現:

settings = LazySettings()# django.conf.__init__.py class LazySettings(LazyObject):# other functions ...def _setup(self, name=None):settings_module = os.environ.get(ENVIRONMENT_VARIABLE)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)def __getattr__(self, name):if self._wrapped is empty:self._setup(name)return getattr(self._wrapped, name)# other functions ...

  _setup方法從當前環境變量中獲取ENVIRONMENT_VARIABLE("DJANGO_SETTINGS_MODULE"),這個值在manage.py文件中已經定義:

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "my_project.settings")

  通過getter/setter方法,對settings對象的操作轉到其私有成員self._wrapped對象的調用上,這里在第一次使用settings對象時,將其私有成員self._wrapped初始化為Settings類實例,其構造函數如下:

# django.conf.__init__.py

class
Settings(BaseSettings):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))# store the settings module in case someone later caresself.SETTINGS_MODULE = settings_modulemod = importlib.import_module(self.SETTINGS_MODULE)tuple_settings = ("ALLOWED_INCLUDE_ROOTS","INSTALLED_APPS","TEMPLATE_DIRS","LOCALE_PATHS",)self._explicit_settings = set()for setting in dir(mod):if setting.isupper():setting_value = getattr(mod, setting)if (setting in tuple_settings andisinstance(setting_value, six.string_types)):raise ImproperlyConfigured("The %s setting must be a tuple. Please fix your settings." % setting)setattr(self, setting, setting_value)self._explicit_settings.add(setting)if not self.SECRET_KEY:raise ImproperlyConfigured("The SECRET_KEY setting must not be empty.")if ('django.contrib.auth.middleware.AuthenticationMiddleware' in self.MIDDLEWARE_CLASSES and'django.contrib.auth.middleware.SessionAuthenticationMiddleware' not in self.MIDDLEWARE_CLASSES):warnings.warn("Session verification will become mandatory in Django 1.10. Please add 'django.contrib.auth.middleware.SessionAuthenticationMiddleware' ""to your MIDDLEWARE_CLASSES setting when you are ready to opt-in after reading the upgrade considerations in the 1.8 release notes.",RemovedInDjango110Warning)if hasattr(time, 'tzset') and self.TIME_ZONE:zoneinfo_root = '/usr/share/zoneinfo'if (os.path.exists(zoneinfo_root) and notos.path.exists(os.path.join(zoneinfo_root, *(self.TIME_ZONE.split('/'))))):raise ValueError("Incorrect timezone setting: %s" % self.TIME_ZONE)os.environ['TZ'] = self.TIME_ZONEtime.tzset()# other functions ...

  這里傳遞給settings_module的參數值為my_project.settings,構造函數會先通過global_settings來設置其屬性,接著讀取my_project.settings,設置其特定的屬性,主要有ALLOWED_INCLUDE_ROOTS、INSTALLED_APPS、TEMPLATE_DIRS、LOCALE_PATHS這幾個key,這幾個key的解釋如下:

  • ALLOWED_INCLUDE_ROOTS, 默認值為 ()?(即空元組,在global_settings中),它表示嵌入文件根路徑的字符串——只有在某字符串存在于該元組的情況下,Django的?{%?ssi?%}?模板標簽才會嵌入以其為前綴的文件。 這樣做是出于安全考慮,從而使模板作者不能訪問到他們不該訪問的文件。
  • INSTALLED_APPS,默認同樣為空元組,它表示項目中哪些 app 處于激活狀態。元組中的字符串,除了django默認自帶的命令之外,就是我們自己定義的app,也就是用python manage.py所啟動的app了。
  • TEMPLATE_DIRS,默認同樣為空元組,它表示模板文件的處處路徑。
  • LOCALE_PATHS,默認同樣為空元組,它表示Django將在這些路徑中查找包含實際翻譯文件的<locale_code>/LC_MESSAGES目錄

  代碼中使用了importlib.import_module這個方法,它支持程序動態引入以'.'分割的目錄層次,比如importlib.import_module('django.core.management.commands.migrate'),這里該方法引入了myproject.settings模塊,加載settings配置文件中上述4個key的值。接著校驗中間件和時區的配置信息,完成全局實例settings中self._wrapped屬性的初始化,最終通過__getattr__方法,將加載到的INSTALLED_APPS信息返回?;氐絜xecute函數,這里的全局settings實例以及初始化完畢,我們的subcommand不是runserver(runserver的情況下來之后再分析),接著運行django.setup()方法:

# django.__init__.py

def
setup():from django.apps import appsfrom django.conf import settingsfrom django.utils.log import configure_loggingconfigure_logging(settings.LOGGING_CONFIG, settings.LOGGING)apps.populate(settings.INSTALLED_APPS)

?  這里setup函數配置日志信息,并且加載settings.INSTALLED_APPS中的自定義模塊以及models模塊,保存在django.apps中,這是一個全局的Apps類實例,用以注冊或者說存儲項目中的INSTALLED_APPS模塊信息。我們來看下apps.populate方法:

class Apps(object):# other functions ...def populate(self, installed_apps=None):if self.ready:returnwith self._lock:if self.ready:returnif self.app_configs:raise RuntimeError("populate() isn't reentrant")for entry in installed_apps:if isinstance(entry, AppConfig):app_config = entryelse:app_config = AppConfig.create(entry)if app_config.label in self.app_configs:raise ImproperlyConfigured("Application labels aren't unique, ""duplicates: %s" % app_config.label)self.app_configs[app_config.label] = app_configcounts = Counter(app_config.name for app_config in self.app_configs.values())duplicates = [name for name, count in counts.most_common() if count > 1]if duplicates:raise ImproperlyConfigured("Application names aren't unique, duplicates: %s" % ", ".join(duplicates))self.apps_ready = Truefor app_config in self.app_configs.values():all_models = self.all_models[app_config.label]app_config.import_models(all_models)self.clear_cache()self.models_ready = Truefor app_config in self.get_app_configs():app_config.ready()self.ready = True# other functions ...

  for循環中,使用AppConfig.create(entry) 加載installed_apps里面的各模塊,并保存在app_cofigs中,注意create方法是AppConfig類的classmethod,用以實現工廠模式,它根據installed_apps中的模塊構造出 AppConfig(app_name, app_module) 這樣的實例,其中app_name表示INSTALLED_APPS中指定的應用字符串,app_module表示根據app_name加載到的module。當加載的模塊中有定義default_app_config時,那么會構造其表示的類對象,例如我們在django項目中會用到的用戶認證鑒權模塊,在INSTALLED_APPS中配置為'django.contrib.auth',當在import_module此模塊時,實際django.contrib.auth是一個python的package,在__init__.py文件中有定義了default_app_config = 'django.contrib.auth.apps.AuthConfig',那么最終會構造apps.py中定義的AuthConfig類實例,這些default_app_config對應的類同樣繼承自AppConfig。在AppConfig實例的初始化方法中,會記錄這些應用的標簽、文件路徑等信息,最終將這些實例會保存在其屬性app_configs中。接著每個AppConfig實例會加載其指定模塊的models,all_models定義為all_models = defaultdict(OrderedDict),defaultdict會創建表示一個類似dict的實例,在構造時可以指定字典中元素值的默認類型,這里用OrderedDict來指定其默認的類型,OrderedDict是dict的子類,它可以記錄元素添加到字典中的順序,保證元素有序,因此在獲取all_models中的元素時,當key不存在時,會創建一個OrderedDict對象,我們來看下models是如何加載的:

for app_config in self.app_configs.values():all_models = self.all_models[app_config.label]app_config.import_models(all_models)MODELS_MODULE_NAME = 'models'def import_models(self, all_models):self.models = all_modelsif module_has_submodule(self.module, MODELS_MODULE_NAME):models_module_name = '%s.%s' % (self.name, MODELS_MODULE_NAME)self.models_module = import_module(models_module_name)

?  在module指定的目錄或者package中,查找是否有定義models模塊,并將其import進來。再回到execute方法中,如果python manage.py之后傳遞的是非help或者version這種幫助信息,那么會執行到語句:

self.fetch_command(subcommand).run_from_argv(self.argv)

?  fetch_command方法內部先通過get_commands方法,從全局的apps對象中獲取之前加載到的INSTALLED_APPS模塊對應的management/commands包:

# django.core.management.__init__.py @lru_cache.lru_cache(maxsize=None) def get_commands():commands = {name: 'django.core' for name in find_commands(upath(__path__[0]))}if not settings.configured:return commandsfor 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 commandsclass ManagementUtility(object):# other functions ...def fetch_command(self, subcommand):commands = get_commands()try:app_name = commands[subcommand]except KeyError:# This might trigger ImproperlyConfigured (masked in get_commands) settings.INSTALLED_APPSsys.stderr.write("Unknown command: %r\nType '%s help' for usage.\n" %(subcommand, self.prog_name))sys.exit(1)if isinstance(app_name, BaseCommand):# If the command is already loaded, use it directly.klass = app_nameelse:klass = load_command_class(app_name, subcommand)return klass# other functions ...

?

  注意方法定義在django.core.management._init_.py文件中,get_commands方法中的__path__[0]是其__init__.py的絕對路徑,這里通過find_commands首先將django.core.management.commands目錄下的模塊引入進來,像我們常用的一些基礎模塊(通過python manage.py進行調用)比如startpp、migrate、compilemessages、runserver、shell等都在此目錄下。加載完這些基礎模塊之后,接著加載apps中的自定義的commands模塊,即INSTALLED_APPS對應的各個模塊。再根據subcommand從中這些包中獲取到對應的Command,返回Command類對象。django后臺服務中的Command繼承自BaseCommand,并且實現了各自業務的handle方法。

  接著,通過返回的對象調用其run_from_argv方法,從名稱可以看出,這個方法是通過命令行參數,進行函數調用的:

def run_from_argv(self, argv):self._called_from_command_line = Trueparser = self.create_parser(argv[0], argv[1])if self.use_argparse:options = parser.parse_args(argv[2:])cmd_options = vars(options)# Move positional args out of options to mimic legacy optparseargs = cmd_options.pop('args', ())else:options, args = parser.parse_args(argv[2:])cmd_options = vars(options)handle_default_options(options)try:self.execute(*args, **cmd_options)except Exception as e:if options.traceback or not isinstance(e, CommandError):raiseif isinstance(e, SystemCheckError):self.stderr.write(str(e), lambda x: x)else:self.stderr.write('%s: %s' % (e.__class__.__name__, e))sys.exit(1)finally:connections.close_all()

?  我們知道 fetch_command 返回的Command對象繼承自BaseCommand,那么不同的后臺任務可能需要不同的參數信息,在run_from_argv方法中,通過調用create_parser方法,Command子類將不同的參數信息進行設置,再通過執行execute方法,最終調用子類Command對象中定義的handle方法,完成自定義項目中業務邏輯的實現。

?

posted on 2017-02-16 16:30 Tourun 閱讀(...) 評論(...) 編輯 收藏

轉載于:https://www.cnblogs.com/Tour/p/6403833.html

總結

以上是生活随笔為你收集整理的django源码简析——后台程序入口的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。