创建程序实例
工厂函数创建程序实例
在 OOP(Object-Oriented Programming,面向对象编程)中,工厂(factory)是指创建其他对象的对象,通常是一个返回其他类的对象的函数或方法。在 playblog 程序的新版本中,程序实例在工厂函数中创建,这个函数返回程序实例 app。按照惯例,这个函数被命名为 create_app() 或 make_app()。我们把这个工厂函数称为程序工厂(Application Factory)——即 “生产程序的工厂”,使用它可以在任何地方创建程序实例。
工厂函数使得测试和部署更加方便。不必将加载的配置写死在某处,而是直接在不同的地方按照需要的配置创建程序实例。通过⽀持创建多个程序实例,工厂函数提供了很大的灵活性。
新建项目包
整个项目的内容会比较多,最好是把项目的内容单独放在一个包里面单独处理。
# filename: forum/__init__.py
import os
from flask import Flask
from configs import config
from extensions import db
from utils import setup_log
def create_app(config_name=None):
if not config_name:
config_name = os.getenv("FLASK_CONFIG", "development")
app = Flask('zhengxin_flask_forum')
app.config.from_object(config[config_name])
setup_log(config_name)
# 注册插件
db.init_app(app)
return app
除了扩展初始化操作,还有很多处理函数要注册到程序上,比如错误处理函数、上下文处理函数等。虽然蓝图也可以注册全局的处理函数,但是为了便于管理,除了蓝图特定的处理函数,这些处理函数一般都放到工厂函数中注册。
为了避免把工厂函数弄得太长太复杂,可以根据类别把这些代码分离成多个函数,这些函数接收程序实例 app 作为参数,分别用来为程序实例初始化扩展、注册蓝图、注册错误处理函数、注册上下文处理函数等一系列操作
工厂函数接收配置名作为参数,返回创建好的程序实例。如果没有传入配置名,我们会从 FLASK_ENV 环境变量获取,如果没有获取到则使用默认值 development。
通用组件文件夹
在项目中我们会自己写一个通用的方法、类、常量之类的通用内容。当项目变得越来越大时,通用的内容就会越来越多,这些内容可以放在同一个文件下方便管理。
在项目的根目录下创建一个 com
文件夹,然后把 utils.py
拖过去。
因为 app 程序已经移动到 forum
目录下,所以需要修改 .flaskenv
中 app
的使用地址
FLASK_APP="forum:create_app()"
使用蓝图模块化程序
当某一个模块包含太多代码时,常见的做法是将单一模块升级为包, 然后把原模块的内容分离成多个模块。
接来下来我们学习使用蓝图进行模块划分,在进行模块划分之前,我们先来了解一下蓝图的使用
Flask 提供的 Blueprint 类就创建一个蓝图实例。像程序实例一 样,我们可以为蓝图实例注册路由、错误处理函数、上下文处理函数,请求处理函数,甚至是单独的静态文件文件夹和模板文件夹。在使用上,它和程序实例也很相似。比如,蓝图实例同样拥有一个 route() 装饰器,可以用来注册路由,但实际上蓝图对象和程序对象却有一些不一样。
使用蓝图不仅仅是对视图函数分类,而是将程序某一部分的所有操作组织在一起。这个蓝图实例以及一系列注册在蓝图实例上的操作的集合被称为一个蓝图。只有当你把它注册到程序上时,它才会把物体相应的部分印刻出来 —— 把蓝图中的操作附加到程序上。
使用蓝图可以将程序模块化(modular)。一个程序可以注册多个蓝图,我们可以把程序按照功能分离成不同的组件,然后使用蓝图来组织这些组件。蓝图不仅仅是在代码层面上的组织程序,还可以在程序层面上定义属性,具体的形式即为蓝图下的所有路由设置不同的 URL 前缀或子域名。
创建蓝图
蓝图一般在子包中创建,比如创建一个 index 子包,然后在构造文件中创建蓝图实例,使用包管理蓝图允许你设置蓝图独有的静态文件和模板, 并在蓝图内对各类函数分模块存储。
蓝图实例 blog_bp 在 __init__.py
脚本顶端创建实例对象
# filename: forum/views/index.py
from flask import Blueprint
index_bp = Blueprint('index', __name__)
在上面的代码中,从 Flask 导入 Blueprint 类,实例化这个类就获得了蓝图对象。构造方法中的第一个参数是蓝图的名称;第二个参数是包或模块的名称,可以使用 __name__
变量。Blueprint 类的构造函数还接收其他参数来定义蓝图。
然后再写视图函数代码
@index_bp.route('/')
def hello_world():
return 'Hello World!'
视图函数与蓝图对象分开写的原因是一个项目中可能会存在很多的视图,如果写在一个文件里面,到后期维护会变得非常麻烦。最后还要在 __init__.py
文件中导入一下视图文件,将创建的视图方法挂载到蓝图对象上面。
注册蓝图
我们在本章开始曾把蓝图比喻成模子,为了让这些模子发挥作用,我们需要把蓝图注册到程序实例上:
from forum.views import index_bp
# filename: forum/__init__.py
from flask import Flask
from configs import config
from extensions import db
from forum.com.utils import setup_log
from forum.views import index_bp
def create_app(config_name='development'):
app = Flask('zhengxin_forum')
app.config.from_object(config[config_name])
setup_log(config_name)
# 注册插件
db.init_app(app)
app.register_blueprint(index_bp)
return app
蓝图使用 Flask.register_blueprint() 方法注册,必须传入的参数是我们在上面创建的蓝图对象。其他的参数可以用来控制蓝图的行为。比如,我们使用 url_prefix 参数为 auth 蓝图下的所有视图 URL 附加一个 URL 前缀;当然也可以选择不加。
app.register_blueprint(auth_bp, url_prefix='/index')
这时,index_bp 蓝图下的视图的 URL 前都会添加一个 /index
前缀,比如 profile 视图的 URL 规则会变为 /index/profile
。
但是以为会有很多个蓝图都需要注册,最好是将所有的蓝图注册写到一个函数里面,然后再暴露出去进行注册
# filename: forum/views/__init__.py
from .index import index_bp
def register_blueprint(app):
app.register_blueprint(index_bp)
最终代码:
# filename: forum/__init__.py
from forum.views import register_blueprint
...
def create_app(config_name=None):
...
# 注册蓝图
register_blueprint(app)
return app
模块化插件管理
在 forum 目录下新建 extensions 目录
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
from .init_db import db
def register_extensions(app):
db.init_app(app)
然后在项目中引入
# filename: forum/__init__.py
from forum.extensions import register_extensions
def create_app(config_name=None):
...
# 注册拓展插件
register_extensions(app)
return app