使用工厂函数创建程序实例

使用蓝本还有一个重要的好处,那就是允许使用工厂函数来创建程序实例。在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')
app.config.from_object(config[config_name]) app.register_blueprint(auth, url_prefix = '/auth')
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()方法,传入程序实例完成初始化操作:

from personalBlog.extensions 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')
app.config.from_object(config[config_name]) app.register_blueprint(auth, url_prefix = '/auth') 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: 组织工厂函数

from personalBlog.extensions 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')
app.config.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):
app.register_blueprint(auth, url_prefix = '/auth') 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):
@app.errorhandler(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
使用工厂函数后,我们会遇到一个问题:对于蓝本实例没有提供,程序实例独有的属性和方法应该如何调用呢(比如获取配置的app.config属性)?考虑下面的因素:
1-使用工厂函数创建程序实例后,在其他模块中并没有一个创建好的程序实例可以让我们导入使用。
2-使用工厂函数后,程序实例可以在任何地方被创建。你不能固定导入某一个程序实例,因为不同程序实例可能加载不同的配置变量。
解决方法是使用current_app对象,它是一个表示当前程序实例的代理对象。当某个程序实例被创建并运行时,它会自动指向当前运行的程序实例,并把所有操作都转发到当前的程序实例。比如,当我们需要获取配置值时,会使用current_app.config,其他方法和属性也一样。
current_app是程序上下文全局变量,所以只有在激活了程序上下文之后才可以使用。比如在视图函数中,或是在视图函数中调用的函数和对象中。

flask实战-个人博客-使用工厂函数创建程序实例 --的更多相关文章

  1. 【flask】使用类组织配置-使用工厂函数创建程序实例

    [需求] 使用配置类管理flask管理测试环境, 通过1个参数即可控制Flask是运行develpment环境还是production环境(数据库配置,邮件配置也要根据环境的变化而变化) [思路] 1 ...

  2. flask实战-个人博客-虚拟环境、项目结构

    个人博客 博客是典型的CMS(Content Management system,内容管理系统),通常由两部分组成:一部分是博客前台,用来展示开放给所有用户的博客内容:另一部分是博客后台,这部分内容仅 ...

  3. flask实战-个人博客-电子邮件支持

    电子邮件支持 因为博客要支持评论,所以我们需要在文章有了新评论后发邮件通知管理员.而且,当管理员回复了读者的评论后,也需要发送邮件提醒读者. 为了方便读者使用示例程序,personalBlog中仍然使 ...

  4. flask实战-个人博客-使用蓝本模块化程序

    使用蓝本模块化程序 实例化flask提供的blueprint类就创建一个蓝本实例.像程序实例一样,我们可以为蓝本实例注册路由.错误处理函数.上下文处理函数,请求处理函数,甚至是单独的静态文件文件夹和模 ...

  5. flask实战-个人博客-使用类组织配置

    使用类组织配置 在实际需求中,我们往往需要不同的配置组合.例如,开发用的配置,测试用的配置,生产环境用的配置.为了能方便地在这些配置中切换,你可以把配置文件升级为包,然后为这些使用场景分别创建不同的配 ...

  6. flask实战-个人博客-模板 --

    模板 personalBlog采用典型的博客布局,左侧三分之二为主体,显示文章列表.正文:右侧三分之一为边栏,显示分为类列表.社交链接等.现在的工作是将HTML文件加工为模板,并创建对应的表单类,在模 ...

  7. flask实战-个人博客-编写博客前台

    编写博客前台 博客前台需要开放给所有用户,这里包括显示文章列表.博客信息.文章内容和评论等功能功能. 分页显示文章列表 为了在主页显示文章列表,我们要先在渲染主页模板的index视图的数据库中获取所有 ...

  8. flask实战-个人博客-数据库-生成虚拟数据 --

    3.生成虚拟数据 为了方便编写程序前台和后台功能,我们在创建数据库模型后就编写生成虚拟数据的函数. 1)管理员 用于生成虚拟管理员信息的fake_admin()函数如下所示: personalBlog ...

  9. flask实战-个人博客-表单

    表单 下面我们来编写所有表单类,personalBlog中主要包含下面这些表单: 登录表单: 文章表单: 评论表单: 博客设置表单: 这里仅介绍登录表单.文章表单.分类表单和评论表单,其他的表单在实现 ...

随机推荐

  1. Java stream 并发应用案例

    在磁盘目录下有几十个txt文件,里面存储着XML格式的数据,每个文件在2-3M左右,现在需要将以上文件解析出来保存到mysql数据库,总数据量大概在30万条左右. (1)首先通过stream并发解析T ...

  2. maven 向私服部署jar

    1.有源码的情况下 首先需要在要deploy的项目pom中添加私服地址 <distributionManagement> <repository> <id>nexu ...

  3. cad.net 获取所有已经安装的cad版本信息

    计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Autodesk\Hardcopy var ackey = Registry.LocalMachine.OpenSubKey(@&quo ...

  4. 深入理解JVM-类加载初始化阶段-类的主动与被动引用

    JVM的类加载阶段中初始化阶段 P210 虚拟机规定的五种情况必须对类的“初始化”情况 1.遇到new.getstatic.putstatic.或invokestic 四条字节码指令时,如果类没有经过 ...

  5. phpcms新建模板页教程

    phpcms新建模板页教程1 直接去template文件夹里的复制的模板页 比方说list1.html2 去后台 界面模板风格 default 默认模板 点击详情列表 找到list1.htm 设置中文 ...

  6. SQL join 三种扩展用法

    以前学习,只知道  LEFT JOIN.RIGHT JOIN.INNER JOIN.FULL  JOIN,共四种集合,然而加上一些条件,可以组合成另外三种集合,直接上图.

  7. 原生Ajax的怎么用?

    <script> function createXMLHttpRequest() { var xmlhttp; try { //先直接创建XMLHttpRequest xmlhttp = ...

  8. 手机上的unity路径

    转载自:https://www.xuanyusong.com/archives/2656 Application.dataPath路径在PC上无论是Editor还是运行时毫无压力非常万能,但是在手机上 ...

  9. 【C++】一个指针占几个字节?为什么呢?

    一个指针在32位操作系统上,占4个字节 一个指针在64位操作系统上,占8个字节 但是,编译器为了兼容32位操作系统和64位操作系统,所以指针都是4个字节长度 为什么呢? 在计算机中,CPU不能直接与硬 ...

  10. sql中筛选第一条记录【分组排序】

    问题描述 我们现在有一张表titles,共有4个字段,分别是emp_no(员工编号),title(职位),from_date(起始时间),to_date(结束时间),记录的是员工在某个时间段内职位名称 ...