为什么建议在Flask.run上使用flask CLI?


13

在Flask 0.11中flask,引入了CLI。docs和changelog都建议这样做。

开发服务器文档

从Flask 0.11开始,有多种内置方法来运行开发服务器。最好的一个是flask命令行实用程序,但是您也可以继续使用该Flask.run()方法。

命令行

强烈建议使用flask命令行脚本(命令行界面)进行开发,因为由于其加载应用程序的方式,它提供了卓越的重新加载体验。基本用法是这样的:

$ export FLASK_APP=my_application
$ export FLASK_DEBUG=1
$ flask run

更新日志

  • 添加flaskflask.cli用于通过单击CLI系统启动本地调试服务器的模块。与旧flask.run()方法相比,建议使用此方法,因为它采用不同的设计,因此可以更快,更可靠地工作,并且可以替代Flask-Script

到目前为止,我还没有注意到这种“高级重装体验”。我看不到在自定义脚本上使用CLI的意义。

如果使用Flask.run,我只需编写一个python文件:

#!/usr/bin/env python3
from my_app import app


if __name__ == '__main__':
    app.run(debug=True)

如果使用CLI,则必须指定环境变量。在CLI文档中声明可以将其集成到activatevirtualenvwrapper脚本中。我个人认为这是应用程序的一部分,并认为它应该在版本控制下。las,需要一个shell脚本:

#!/usr/bin/env bash
export FLASK_APP=my_app:app
export FLASK_DEBUG=1

flask run

当然,任何Windows用户开始协作后,都将附带一个附加的bat脚本。

同样,第一个选项允许在启动实际应用之前使用Python编写的设置。

例如,这允许

  • 在Python中解析命令行参数
  • 在运行应用程序之前设置日志记录

他们似乎在提倡添加自定义命令。我看不出为什么这比编写简单的Python脚本更好,为什么可以通过入口点公开这些脚本。

通过Python运行脚本使用已配置的记录器时的示例记录输出:

$ ./run.py 
   DEBUG 21:51:22 main.py:95) Configured logging
    INFO 21:51:22 _internal.py:87)  * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
    INFO 21:51:22 _internal.py:87)  * Restarting with inotify reloader
   DEBUG 21:51:22 main.py:95) Configured logging
 WARNING 21:51:22 _internal.py:87)  * Debugger is active!
    INFO 21:51:22 _internal.py:87)  * Debugger pin code: 263-225-431
   DEBUG 21:51:25 inotify_buffer.py:61) in-event <InotifyEvent: src_path=b'my_app/main.py', wd=272, mask=IN_MODIFY, cookie=0, name=b'main.py'>
   DEBUG 21:51:25 inotify_buffer.py:61) in-event <InotifyEvent: src_path=b'my_app/main.py', wd=272, mask=IN_MODIFY, cookie=0, name=b'main.py'>
    INFO 21:51:25 _internal.py:87)  * Detected change in 'my_app/main.py', reloading
    INFO 21:51:26 _internal.py:87)  * Restarting with inotify reloader
   DEBUG 21:51:26 main.py:95) Configured logging
 WARNING 21:51:26 _internal.py:87)  * Debugger is active!
    INFO 21:51:26 _internal.py:87)  * Debugger pin code: 263-225-431

在通过CLI:使用配置的记录器时记录输出示例:请注意,在此过程中无法足够早地设置根记录器。

$ ./run.sh 
 * Serving Flask app "appsemble.api.main:app"
 * Forcing debug mode on
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with inotify reloader
   DEBUG 21:51:33 main.py:95) Configured logging
 * Debugger is active!
 * Debugger pin code: 187-758-498
   DEBUG 21:51:34 main.py:95) Configured logging
   DEBUG 21:51:37 inotify_buffer.py:61) in-event <InotifyEvent: src_path=b'my_app/main.py', wd=272, mask=IN_MODIFY, cookie=0, name=b'main.py'>
   DEBUG 21:51:37 inotify_buffer.py:61) in-event <InotifyEvent: src_path=b'my_app/main.py', wd=272, mask=IN_MODIFY, cookie=0, name=b'main.py'>
 * Detected change in 'my_app/main.py', reloading
    INFO 21:51:37 _internal.py:87)  * Detected change in 'my_app/main.py', reloading
 * Restarting with inotify reloader
    INFO 21:51:38 _internal.py:87)  * Restarting with inotify reloader
 * Debugger is active!
 * Debugger pin code: 187-758-498
   DEBUG 21:51:38 main.py:95) Configured logging

我的实际问题很简单:

为什么建议使用烧瓶CLI Flask.run

Answers:


11

在开发服务器文档中,他们指出调用run()并自动重新加载代码存在问题:

这在通常情况下效果很好,但在开发中效果不佳,这就是为什么从Flask 0.11开始推荐使用flask方法的原因。这样做的原因是,由于重载机制的工作方式,所以有一些怪异的副作用(例如两次执行某些代码,有时在没有消息的情况下崩溃,或者在发生语法或导入错误时死亡)。

他们声称CLI不会遇到此问题。

似乎涉及此问题的第一个提交是:https : //github.com/pallets/flask/commit/3bdb90f06b9d3167320180d4a5055dcd949bf72f

阿明·罗纳彻(Armin Ronacher)写道:

不建议使用此功能进行自动重载的开发,因为对此功能的支持很差。相反,您应该使用flask命令行脚本的runserver支持。

正如亚伦·霍尔(Aaron Hall)所提到的那样,由于实际上不会重新实例化所有作为对象(在替换模块中定义的类的实例)的对象,并且每次重新加载模块时,似乎都无法使用run()。它导入的模块也不会重新加载。

可以在以下位置找到有关Python 3的详细信息:https : //docs.python.org/3/library/importlib.html?highlight=importlib#module-importlib

它指出:

与Python中的所有其他对象一样,旧对象仅在其引用计数降至零后才被回收。

对旧对象的其他引用(例如模块外部的名称)不会反弹以引用新对象,并且如果需要的话,必须在出现它们的每个命名空间中进行更新。

重新加载模块时,将保留其字典(包含模块的全局变量)。名称的重新定义将覆盖旧的定义,因此通常这不是问题。如果模块的新版本未定义旧版本定义的名称,则保留旧定义。

因此,通过创建一个新进程并删除旧进程,您自然会消除所有过时的引用。

另外,Flask的CLI使用'click'模块,使添加自定义命令非常容易,但是最重要的是,除了修复重新加载的错误之外,CLI还提供了一种标准化的方式来运行应用程序和添加自定义命令。这听起来很不错,因为它可以使Flask的熟悉程度在不同团队和应用程序之间更容易转移,而不是采用多种方式来完成同一件事。

这似乎是使Flask更符合Python Zen的一种真正方法:

应该有一种-最好只有一种-显而易见的方法。


2
我认为Armin的意思是“严重支持”:在Python中,重新加载模块不会重新加载该模块导入的模块,也不会重新关联其他模块中的名称,从指向旧对象到新模块中的新对象-因此将新模块热交换到同一过程中是有问题的。当您要更改代码时,最好开始一个新的过程。
亚伦·霍尔

既然您提到了它,我就回想起您描述的行为,感谢您的澄清!我将相应地编辑答案。
Martin Jungblut Schreiner

好,加上1来引用我。:)
亚伦·霍尔

亚伦·霍尔(Aaron Hall)的加入为我澄清了这一点。谢谢。:)
Remco Haszing

7
为什么我们必须使用环境变量FLASK_APP?这是工作原理的本质吗?我很好奇为什么flask run不接受相同的参数,这会使新手入职变得更加容易。谢谢。
约翰·惠勒
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.