django開發者模式中的autoreload是怎樣實現的

来源:http://www.cnblogs.com/beeler/archive/2017/08/17/7382917.html
-Advertisement-
Play Games

在開發django應用的過程中,使用開發者模式啟動服務是特別方便的一件事,只需要 python manage.py runserver 就可以運行服務,並且提供了非常人性化的autoreload機制,不需要手動重啟程式就可以修改代碼並看到反饋。剛接觸的時候覺得這個功能比較人性化,也沒覺得是什麼特別高 ...


    在開發django應用的過程中,使用開發者模式啟動服務是特別方便的一件事,只需要 python manage.py runserver 就可以運行服務,並且提供了非常人性化的autoreload機制,不需要手動重啟程式就可以修改代碼並看到反饋。剛接觸的時候覺得這個功能比較人性化,也沒覺得是什麼特別高大上的技術。後來有空就想著如果是我來實現這個autoreload會怎麼做,想了很久沒想明白,總有些地方理不清楚,看來第一反應真是眼高手低了。於是就專門花了一些時間研究了django是怎樣實現autoreload的,每一步都看源碼說話,不允許有絲毫的想當然:

1、runserver命令。在進入正題之前其實有一大段廢話,是關於runserver命令如何執行的,和主題關係不大,就簡單帶一下:
命令行鍵入 python manage.py runserver 後,django會去尋找runserver這個命令的執行模塊,最後落在
django\contrib\staticfiles\management\commands\runserver.py模塊上:

#django\contrib\staticfiles\management\commands\runserver.py
from django.core.management.commands.runserver import \
Command as RunserverCommand

class Command(RunserverCommand):
  help = "Starts a lightweight Web server for development and also serves static files."

而這個Command的執行函數在這:

#django\core\management\commands\runserver.py
class Command(BaseCommand):
  def run(self, **options):
  """
  Runs the server, using the autoreloader if needed
  """
  use_reloader = options['use_reloader']

  if use_reloader:
    autoreload.main(self.inner_run, None, options)
  else:
    self.inner_run(None, **options)

這裡有關於use_reloader的判斷。如果我們在啟動命令中沒有加--noreload,程式就會走autoreload.main這個函數,如果加了,就會走self.inner_run,直接啟動應用。
其實從autoreload.main的參數也可以看出,它應該是對self.inner_run做了一些封裝,autoreload的機制就在這些封裝當中,下麵我們繼續跟。

PS: 看源碼的時候發現django的command模式還是實現的很漂亮的,值得學習。

2、autoreload模塊。看autoreload.main():

#django\utils\autoreload.py:
def main(main_func, args=None, kwargs=None):
  if args is None:
    args = ()
  if kwargs is None:
    kwargs = {}
  if sys.platform.startswith('java'):
    reloader = jython_reloader
  else:
    reloader = python_reloader

  wrapped_main_func = check_errors(main_func)
  reloader(wrapped_main_func, args, kwargs)

這裡針對jpython和其他python做了區別處理,先忽略jpython;check_errors就是把對main_func進行錯誤處理,也先忽略。看python_reloader:

#django\utils\autoreload.py:
def python_reloader(main_func, args, kwargs):
  if os.environ.get("RUN_MAIN") == "true":
    thread.start_new_thread(main_func, args, kwargs)
    try:
      reloader_thread()
    except KeyboardInterrupt:
      pass
  else:
    try:
      exit_code = restart_with_reloader()
      if exit_code < 0:
        os.kill(os.getpid(), -exit_code)
      else:
        sys.exit(exit_code)
    except KeyboardInterrupt:
      pass

第一次走到這裡時候,環境變數中RUN_MAIN變數不是"true", 甚至都沒有,所以走else, 看restart_with_reloader:

#django\utils\autoreload.py:
def restart_with_reloader():
    while True:
      args = [sys.executable] + ['-W%s' % o for o in sys.warnoptions] + sys.argv
    if sys.platform == "win32":
      args = ['"%s"' % arg for arg in args]
    new_environ = os.environ.copy()
    new_environ["RUN_MAIN"] = 'true'
    exit_code = os.spawnve(os.P_WAIT, sys.executable, args, new_environ)
    if exit_code != 3:
      return exit_code    

這裡首先起一個while迴圈, 內部先把RUN_MAIN改成了"true",然後用os.spawnve方法開一個子進程(subprocess),看看os.spawnve的說明:

#os.py
def
spawnve(mode, file, args, env):   """spawnve(mode, file, args, env) -> integer   Execute file with arguments from args in a subprocess with the   specified environment.   If mode == P_NOWAIT return the pid of the process.   If mode == P_WAIT return the process's exit code if it exits normally;   otherwise return -SIG, where SIG is the signal that killed it. """
  return _spawnvef(mode, file, args, env, execve)

其實就是再調一遍命令行,又走了一遍 python manage.py runserver。

接著看restart_with_reloader里的while迴圈,需要註意的是while迴圈退出的唯一條件是exit_code!=3。 如果子進程不退出,就一直停在 os.spawnve這一步; 如果子進程退出,而退出碼不是3,while就被終結了;如果是3,繼續迴圈,重新創建子進程。從這個邏輯可以猜想autoreload的機制:當前進程(主進程)其實啥也不幹,就監視子進程的運行狀況,子進程才是真正幹事兒的;如果子進程以exit_code=3退出(應該由於檢測到了文件修改),就再啟動一遍子進程,新代碼自然就生效了;如果子進程以exit_code!=3退出,主進程也結束,整個django程式就算跪了。這隻是猜想,下麵接著來驗證。

3、子進程。上面其實有一個疑問,既然是重新啟動了一次,為什麼子進程不會接著生成子進程?原因就在於RUN_MAIN這個環境變數,主進程中把它改成了true,子進程走到python_reloader函數的時候:

#django\utils\autoreload.py:
def python_reloader(main_func, args, kwargs):
  if os.environ.get("RUN_MAIN") == "true":
    thread.start_new_thread(main_func, args, kwargs)
    try:
      reloader_thread()
    except KeyboardInterrupt:
      pass
  else:
    try:
      exit_code = restart_with_reloader()
      if exit_code < 0:
        os.kill(os.getpid(), -exit_code)
      else:
        sys.exit(exit_code)
    except KeyboardInterrupt:
      pass

if條件滿足了,和主進程走了不一樣的邏輯分支。在這裡,首先去開一個線程,運行main_func,就是上文的 Command.inner_run。這裡的thread模塊是這麼import的:

#django\utils\autoreload.py:
from django.utils.six.moves import _thread as thread

這裡six模塊的作用是相容各種python版本:

[codeblock six]
#django\utils\six.py
class _SixMetaPathImporter(object):

"""
A meta path importer to import six.moves and its submodules.

This class implements a PEP302 finder and loader. It should be compatible
with Python 2.5 and all existing versions of Python3
"""

官網說明:
# https://pythonhosted.org/six/
Six: Python 2 and 3 Compatibility Library
Six provides simple utilities for wrapping over differences between Python 2 and Python 3. It is intended to support codebases that work on both Python 2 and 3 without modification. six consists of only one Python file, so it is painless to copy into a project.

所以如果程式想在python2和python3上都能跑,且魯邦,six是重要的工具。之後抽個時間看下six,mark一下。

然後再開一個reloader_thread:

[codeblock autoreload_reloader_thread]
#django\utils\autoreload.py:
def reloader_thread():
  ensure_echo_on()
  if USE_INOTIFY:
    fn = inotify_code_changed
  else:
    fn = code_changed
  
while RUN_RELOADER:     change = fn()     if change == FILE_MODIFIED:       sys.exit(3) # force reload     elif change == I18N_MODIFIED:       reset_translations()     time.sleep(1)

ensure_echo_on()其實還沒看明白,貌似是針對類unix系統文件處理的,先略過;
USE_INOTIFY也是系統文件操作相關的變數,根據 inotify 是否可用選擇檢測文件變化的方法。
while迴圈,每隔1秒檢測一下文件狀態,如果是普通文件有變化,進程退出,退出碼為3,主進程一看:退出碼是3,就重啟子進程。。。。這樣就和上面連上了;如果不是普通文件變化,而是I18N_MODIFIED(.mo尾碼的文件變化,二進位庫文件之類的),那就 reset_translations ,大概意思是把已載入過的庫緩存清理掉,下次重新載入。

  以上就是autoreload機制的流程。其中還是有些細節不是特別清楚,比如不同操作系統文件變化的檢測,但都是很細節的東西了,不涉及主流程。看完這些,我又問了自己一遍,如果是讓我設計autoreload機制會怎樣搞。現在我的答案是:直接把 django\utils\autoreload.py 文件拿來用啊。其實這是很獨立的一個模塊,而且特別通用,完全可以作為通用的autoreload解決方案,我還自己寫個毛啊。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.Rock Paper Scissors(剪刀 石頭 布) 說明:在電腦科學中有一種特別重要的游戲,因為它本身很簡單,可以用來創建非常狡猾的人工智慧演算法來對抗人類(或彼此),預測對手的行為。 這個古老的游戲是在兩個參與者之間進行的,他們同時用他們的手勢——石頭,紙或者剪刀。如果兩人都投了相同的手 ...
  • 昨天有個大牛說我啰嗦,眼光比較細碎,看不到重點。太他爺爺的有道理了!要說看人品,還是女孩子強一些。原來記得看到一個男孩子的抱怨,說怎麼兩人剛剛開始在一起,女孩子在心裡就已經和他過完了一輩子。哥哥們,不想這麼遠行嗎?看看何潔,看看帶著倆娃跳樓的媽媽。所以現在的女孩子是很明白的,有些男孩子個子不高,其貌 ...
  • 關於Spring總結 Spring引入 傳統的基於mvc的項目框架結構:Entity / dao / service / action 簡單用戶訪問流程:/user.action Tomcat (伺服器創建Action、Service、dao 引出思考: 1. 對象創建創建能否寫死? 2. 對象創建 ...
  • 引言 記憶體管理一直是JAVA語言自豪與驕傲的資本,它讓JAVA程式員基本上可以徹底忽略與記憶體管理相關的細節,只專註於業務邏輯。不過世界上不存在十全十美的好事,在帶來了便利的同時,也因此引入了很多令人抓狂的記憶體溢出和泄露的問題。 可怕的事情還不只如此,有些使用其它語言開發的程式員,給JAVA程式員扣上 ...
  • 一springmvc項目中我新增記錄完全ok,編輯就是不行,後臺方法進不去。老是報錯HTTP ERROR 400 Bad Request。 經過查詢,說是400表示請求中的語法錯誤。 我把新增記錄的請求信息拷貝下來,把編輯的請求信息也拷貝下來,然後用notepad++比較兩個請求,終於發現原因就是i ...
  • 第一種原因: no Session 錯誤 dao層中get方法換成了load方法,或者其他原因引起. 原因分析: 真正用到代理對象的時候,代理對象沒有值,並且session的生命周期已經走完了. 解決方案:1,load()換成get(),或者立即查詢,比如列印一下. 2,延長session的存活時間 ...
  • spl_autoload_register() 函數可以註冊任意數量的自動載入器,當使用尚未被定義的類(class)和介面(interface)時自動去載入。通過註冊自動載入器,腳本引擎在 PHP 出錯失敗前有了最後一個機會載入所需的類。_autoload()自動載入類:當我們實例化一個未定義的類時... ...
  • 1.工廠模式,在各種BeanFactory以及ApplicationContext創建中都用到了 2.模版模式,在各種BeanFactory以及ApplicationContext實現中也都用到了 3.代理模式,Spring AOP 利用了 AspectJ AOP實現的! AspectJ AOP 的 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...