使⽤⼯⼚函数创建程序实例
使⽤蓝本还有⼀个重要的好处,那就是允许使⽤⼯⼚函数来创建程序实例。在OOP(Object-Oriented Programming,⾯向对象编程)中,⼯⼚(factory)是指创建其他对象的对象,通常是⼀个返回其他类的对象的函数或⽅法,⽐如我们之前的例⼦中创建的WTForms验证器(函数)。在personalBlog程序中,程序实例可以设计为在⼯⼚函数中创建,这个函数返回程序实例app。按照惯例,这个函数被命名为create_app()或make_app()。我们把这个⼯⼚函数称为程序⼯⼚(Application Factory)--即“⽣产程序的⼯⼚”,使⽤它可以在任何地⽅创建程序实例。
⼯⼚函数使得测试和部署更加⽅便。我们不必将加载的配置写死在某处,⽽是直接在不同的地⽅按照需要的配置创建程序实例。通过⽀持创建多个程序实例,⼯⼚函数提供了很⼤的灵活性。另外,借助⼯⼚函数,我们还可以分离扩展的初始化操作。创建扩展对象的操作可以分离到单独的模块,这样可以有效减少循环依赖的发⽣。personalBlog程序的⼯⼚函数如下所⽰:
personalBlog/__init__.py
from flask import Flask
from personalBlog.settings import config
def create_app(config_name=None):
if config_name is None:
config_name = os.getenv('FLASK_CONFIG', 'development')
app = Flask('personalBlog')
return app
⼯⼚函数接收配置名作为参数,返回创建好的程序实例。如果没有传⼊配置名,我们会从FLASK_CONFIG环境变量获取,如果没有获取到则使⽤默认值development。
在这个⼯⼚函数中,我们会创建程序实例,然后为其加载配置,注册在前⾯创建的蓝本,最后返回程序实例。不过,现在的程序实例还没有执⾏扩展的初始化操作,后续⼀步步扩充它。
河南景点⼯⼚函数⼀般在程序包的构造⽂件中创建,如果你愿意,也可以在程序包内新建的模块来存放,⽐如factory.py或是app.py。
1、加载配置
五一放假通知格式范文⼯⼚函数接收配置名称作为参数,这允许我们在程序的不同位置传⼊不同的配置来创建程序实例。⽐如,使⽤⼯⼚函数后,我们可以在测试脚本中使⽤测试配置来调⽤⼯⼚函数,创建⼀个单独⽤于测试的程序实例,⽽不⽤从某个模块导⼊程序实例。
2、初始化扩展
为了完成扩展的初始化操作,我们需要在实例化扩展类时传⼊程序实例。但使⽤⼯⼚函数时,并没有⼀个创建好的程序实例可以导⼊。如果我们把实例化操作放到⼯⼚函数中,那么我们就没有⼀个全局的扩展对象可以使⽤,⽐如表⽰数据库的db对象。
为了解决这个问题,⼤部分扩展都提供了⼀个init_app()⽅法来⽀持分离扩展的实例化和初始化操作。现在我们仍然像往常⼀样初始化扩展类,但是并不传⼊程序实例。这时扩展类实例化的⼯作可以集中放到extension.py脚本中,如下所⽰:
personalBlog/extensions.py:扩展类实例化
from flask_bootstrap import Bootstrap
from flask_sqlalchemy import SQLAlchemy
from flask_mail_import Mail
from flask_ckeditor import CKEditor
from flask_moment import Moment
bootstrap = Bootstrap()
db = SQLAlchemy()
moment = Moment()
ckeditor = CKEditor()
mail = Mail()
现在,当我们需要在程序实例中使⽤扩展对象时,直接从这个extensions模块导⼊即可。在⼯⼚函数中,我们导⼊所有扩展对象,并对其调⽤init_app()⽅法,传⼊程序实例完成初始化操作:
sions import bootstrap, db, moment, ckeditor, mail
def create_app(config_name=None):
if config_name is None:
config_name = os.getenv('FLASK_CONFIG', 'development')
app = Flask('personalBlog')
bootstrap.init_app(app)
db.init_app(app)
moment.init_app(app)
ckeditor.init_app(app)
mail.init_app(app)
return app
杰克 尼科尔森3、组织⼯⼚函数
除了扩展初始化操作,还有很多处理函数要注册到程序上,⽐如错误处理函数、上下⽂处理函数等。虽然蓝本也可以注册全局的处理函数,但是为了便于管理,除了蓝本特定的处理函数,这些处理函数⼀般都放到⼯⼚函数中注册。
为了避免把⼯⼚函数弄得太长太复杂,我们可以根据类别把这些代码分离成多个函数,这些函数接收程序实例app作为参数,分别⽤来为程序实例初始化扩展、注册蓝本、注册错误处理函数、注册上下⽂处理函数等⼀系列操作,如下所⽰:
personalBlog/__init__.py: 组织⼯⼚函数
sions import bootstrap, db, moment, ckeditor, mail, loginManager
def create_app(config_name = None):
if config_name is None:
config_name = os.getenv('FLASK_CONFIG', 'development')
app = Flask('personalBlog')
宝宝取名字fig.from_object(config[config_name])
register_logging(app) # 注册⽇志处理器
register_extensions(app) # 注册扩展(扩展初始化)
register_blueprints(app) # 注册蓝本
register_commands(app) # 注册⾃定义shell命令
register_errors(app) # 注册错误处理函数
register_shell_context(app) # 注册错误处理函数
register_template_context(app) # 注册模板上下⽂处理函数
return app
def register_logging(app):
pass#后续介绍⽇志
def register_extensions(app):
bootstrap.init_app(app)
db.init_app(app)
ckeditor.init_app(app)
mail.init_app(app)
moment.init_app(app)
def register_blueprints(app):
def register_shell_context(app):
@app.shell_context_processor
如何炖鸡汤def make_shell_context():
return dict(db = db)
def register_template_context(app):
pass
def register_errors(app):
@handler(400)
def bad_request(e):
return render_template('errors/400.html'), 400
def register_commands(app):
pass
这⾥的register_*函数的命名只是约定,你也可以使⽤configure_*或类似的命名形式。另外,你也可以按需要添加或删除对应的函数。
现在,当⼯⼚函数被调⽤后。⾸先创建⼀个特定配置类的程序实例,然后执⾏⼀些列注册函数为程序实例注册扩展、蓝本、错误处理器、上下⽂处理器、请求处理器。。。在这个程序⼯⼚的加⼯流⽔线的尽头,我们可以得到⼀个包含所有基本组件的可以直接运⾏的程序实例。
当使⽤⼯⼚函数时,因为扩展初始化操作分离,db.create_all()将依赖于程序上下⽂才能正常执⾏。执⾏flask shell命令启动的python shell会⾃动激活程序上下⽂,flask命令也会默认在程序上下⽂环境下执⾏,所以⽬前程序中的db.create_all()⽅法可以被正确执⾏。当在其他脚本中直接调⽤db.create_all(),或是在普通的python shell中调⽤时,则需要⼿动激活程序上下⽂。
4、启动程序
当使⽤flask run命令启动程序时,Flask的⾃动发现程序实例机制还包含另⼀种⾏为:flask会⾃动从环境变量FLASK_APP的值定义的模块中寻名称为create_app()或make_app()的⼯⼚函数,⾃动调⽤⼯⼚函数创建程序实例并运⾏。因为我们已经在.flaskenv⽂件中将FLASK_APP设为personalBlog,所以不需要更改任何设置,继续使⽤flask run命令即可运⾏程序:
flask run
如果想设置特定的配置名称,最简单的⽅式是通过环境变量FLASK_CONFIG设置。另外,你也可以使⽤FLASK_APP显⽰地指定⼯⼚函数并传⼊参数:
FLASK_APP = "personalBlog:create_app('development')"
为了⽀持Flask⾃动从FLASK_APP环境变量对应值指向的模块或包中发现⼯⼚函数,⼯⼚函数中接收的参数必须是默认参数,即设置了默认值的参数,⽐
如“config_name=None”。
5、current_app
使⽤⼯⼚函数后,我们会遇到⼀个问题:对于蓝本实例没有提供,程序实例独有的属性和⽅法应该如何调⽤呢(⽐如获取配置的fig属性)?考虑下⾯的因素:
1-使⽤⼯⼚函数创建程序实例后,在其他模块中并没有⼀个创建好的程序实例可以让我们导⼊使⽤。
2-使⽤⼯⼚函数后,程序实例可以在任何地⽅被创建。你不能固定导⼊某⼀个程序实例,因为不同程序实例可能加载不同的配置变量。
解决⽅法是使⽤current_app对象,它是⼀个表⽰当前程序实例的代理对象。当某个程序实例被创建并运⾏时,它会⾃动指向当前运⾏的程序实例,并把所有操作都转发到当前的程序实例。⽐如,当我们需要获取配置值时,会使⽤fig,其他⽅法和属性也⼀样。
current_app是程序上下⽂全局变量,所以只有在激活了程序上下⽂之后才可以使⽤。⽐如在视图函数中,或是在视图函数中调⽤的函数和对象中。
发布评论