理解:Flask 在初始化时传入参数的具体作用到底有哪些?
了解或使用过 flask
框架的话,肯定会见过过如下所示的代码,其具体的作用就是实例化 Flask
的对象。因为 Web
服务器使用 WSGI
协议,所以需要把客户端所有的请求都转发给这个程序实例,即这里的 app
实例。
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
系统变量的规则
- 名为
test.py
的模块,其位于应用程序的顶级目录中,那么__name__
变量的值就为test
。 - 如果
test.py
模块位于my_package
的Python
包中,则__name__
的值就为my_package.test
。
- 名为
系统变量的例外
- 在
__init__.py
包构造函数模块中,__name__
的值是包名称,不带__init__
。例如,在my_package/__init__.py
中,__name__
的值就是my_package
。 - 在应用程序的主模块(运行
Python
解释器的文件)中,__name__
的值具有特殊值__main__
。
- 在
1. 查找资源
系统变量的作用 - 查找文件系统上的资源
Flask
类只有一个必须指定的参数,用于指定程序的主模块或者包的名称,即这里通过会使用到的 __name__
这个系统变量。实例中的该系统变量,指的就是本 py
文件的文件名。其可以让 flask.helpers.get_root_path
函数通过传入这个名字确定程序的根目录,以便获得静态文件和模板文件的目录。
# Application Object
class flask.Flask(import_name, \
static_url_path=None, \
static_folder='static', \
static_host=None, \
host_matching=False, \
subdomain_matching=False, \
template_folder='templates', \
instance_path=None, \
instance_relative_config=False, \
root_path=None)
实际上,很容易理解。通过传入的 import_name
参数(即这里的__name__
参数),将其附加到实例化的 app
上下文中,从而告诉 Flask
如何寻找到这些资源文件,比如模板和静态文档等。
它的工作方式如下: Flask
接受了作为 import_name
传入的参数,并试图通过查找具有该名称的模块对象来使用它来确定应用程序的根路径。知道此路径后,它将添加静态目录和模板目录名称,这就是获取这些文件的位置。
>>> from flask.helpers import get_root_path
# the app package
>>> get_root_path('app')
'/home/miguel/microblog/app'
# the flask package
>>> get_root_path('flask')
'/home/miguel/microblog/venv/lib/python3.8/site-packages/flask'
# the config.py module
>>> get_root_path('config')
'/Users/mgrinberg/Documents/dev/python/microblog'
# the app/models.py module
>>> get_root_path('app.models')
'/Users/mgrinberg/Documents/dev/python/microblog/app'
# the app.api package
>>> get_root_path('app.api')
'/Users/mgrinberg/Documents/dev/python/microblog/app/api'
2. 改善调试
系统变量的作用 - 改善 Flask 扩展中的调试信息
弄清楚这一点非常棘手。我发现使用 import_name
参数的唯一 Flask
扩展就是非常有名的 Flask-SQLAlchemy
。该扩展程序提供了一个 get_debug_queries()
函数,该函数收集并记录在请求生存期内发出的所有查询。
记录的属性之一是应用程序源代码中发出查询的位置。获取此信息实际上非常困难,Flask-SQLAlchemy
在查询完成时遍历调用堆栈,直到找到与应用程序导入名称匹配的源位置。尽管这是一种非常酷的技术,但它也非常神奇且晦涩难懂。 我还没有看到它在任何其他 Flask
扩展中使用。
def get_debug_queries():
return getattr(_app_ctx_stack.top, 'sqlalchemy_queries', [])
3. 其他特性
系统变量的作用 - 还有两个特别的使用特点
在 Flask
项目的源代码中查看 import_name
参数的其他用法,发现了另外两个值得一提的使用示例。
- 第一点
就是 Blueprint
类,它将 blueprint
的名称作为第一个参数,将 import_name
作为第二个参数。蓝图中参数的使用与寻找蓝图特定资源有关,它的工作方式与应用程序实例中的工作方式相同。
class Blueprint(_PackageBoundObject):
import_name = None
def __init__(self, name, import_name, ...):
_PackageBoundObject.__init__(
self, import_name, template_folder, root_path=root_path
)
- 第二点
使用 import_name
参数的另一个有趣的地方是为应用程序实例命名。当打印应用程序实例时,将显示应用程序的名称,如下所示。
>>> from flask import Flask
>>> app = Flask('foo')
>>> app
<Flask 'foo'>
这个名称被分配给 Click
命令行界面的主组。如果要将项目的 Flask CLI
作为一个组附加到父 CLI
中,那么这个名称将是你用来访问 Flask
应用程序命令的组名。
@click.command("shell", short_help="Run a shell in the app context.")
@with_appcontext
def shell_command():
import code
from .globals import _app_ctx_stack
app = _app_ctx_stack.top.app
banner = "Python %s on %s\nApp: %s [%s]\nInstance: %s" % (
sys.version,
sys.platform,
app.import_name,
app.env,
app.instance_path,
)
4. 参考文档
送人玫瑰,手有余香