动态加载Python源代码
我现在在玩Flask这个框架,但我搞不清楚它的调试机制是怎么工作的。更具体一点,当我保存我的Python应用程序文件时,我不需要手动重启服务器,它会在我发出请求时自动加载最新的内容。那么我的问题是,运行中的程序是怎么知道文件被改动了,并且能够对这些改动做出反应的呢?
2 个回答
0
内置的 reload()
函数可以帮你完成这个任务。这个函数可能就是 Flask 用来重新加载代码的原因(当它发现代码在磁盘上以某种方式发生了变化时)。
关于这个问题,如何卸载(重新加载)一个 Python 模块? 里有更多的信息。
7
Flask使用了Werkzeug里面的一个叫做run_with_reloader
的功能(在serving.py
文件里找到),这个功能又调用了同一个文件里之前创建的restart_with_reloader
和reloader_loop
这两个功能。
run_with_reloader
会启动另一个Python进程(再次运行Werkzeug,使用你传给第一个进程的所有参数),这个新进程会用thread
模块来创建一个新的线程或子进程,来运行你的服务器功能。然后它会运行reloader_loop
并等待。
reloader_loop
的工作就是循环检查所有已经导入的模块,获取它们的最后修改时间。然后在设定的时间间隔(默认是1秒)内,它会再次检查所有文件,看它们是否被修改过。如果有修改,当前正在运行的(从属)Werkzeug进程会以退出代码3结束。一旦它结束,它启动的线程或子进程(实际上在执行工作的)也会被终止。主进程会检查退出代码是否为3。如果是,它会像之前一样启动一个新的从属子进程。否则,它会以相同的退出代码结束。
这里是参考代码:
def reloader_loop(extra_files=None, interval=1):
"""When this function is run from the main thread, it will force other
threads to exit when any modules currently loaded change.
Copyright notice. This function is based on the autoreload.py from
the CherryPy trac which originated from WSGIKit which is now dead.
:param extra_files: a list of additional files it should watch.
"""
def iter_module_files():
for module in sys.modules.values():
filename = getattr(module, '__file__', None)
if filename:
old = None
while not os.path.isfile(filename):
old = filename
filename = os.path.dirname(filename)
if filename == old:
break
else:
if filename[-4:] in ('.pyc', '.pyo'):
filename = filename[:-1]
yield filename
mtimes = {}
while 1:
for filename in chain(iter_module_files(), extra_files or ()):
try:
mtime = os.stat(filename).st_mtime
except OSError:
continue
old_time = mtimes.get(filename)
if old_time is None:
mtimes[filename] = mtime
continue
elif mtime > old_time:
_log('info', ' * Detected change in %r, reloading' % filename)
sys.exit(3)
time.sleep(interval)
def restart_with_reloader():
"""Spawn a new Python interpreter with the same arguments as this one,
but running the reloader thread.
"""
while 1:
_log('info', ' * Restarting with reloader...')
args = [sys.executable] + sys.argv
new_environ = os.environ.copy()
new_environ['WERKZEUG_RUN_MAIN'] = 'true'
# a weird bug on windows. sometimes unicode strings end up in the
# environment and subprocess.call does not like this, encode them
# to latin1 and continue.
if os.name == 'nt':
for key, value in new_environ.iteritems():
if isinstance(value, unicode):
new_environ[key] = value.encode('iso-8859-1')
exit_code = subprocess.call(args, env=new_environ)
if exit_code != 3:
return exit_code
def run_with_reloader(main_func, extra_files=None, interval=1):
"""Run the given function in an independent python interpreter."""
if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
thread.start_new_thread(main_func, ())
try:
reloader_loop(extra_files, interval)
except KeyboardInterrupt:
return
try:
sys.exit(restart_with_reloader())
except KeyboardInterrupt:
pass