Flask备注4(Structure)
package
通过Flask可以非常简单的通过一个module(一个py文件)创建一个简单的application。这种简单程序的文件结构如下:
/yourapplication /yourapplication.py /static /style.css /templates layout.html index.html login.html ...
这种结构对于较大或者复杂的程序并不合适。对于复杂程序可以通过python自带的package结构来组织代码结构。
包含init.py的文件夹都是package,引用该目录下的所有module都会先导入init.py并执行顶层代码。
使用package的Flask的代码结构如下:
/yourapplication /runserver.py /yourapplication /__init__.py /application.py /views.py /static /style.css /templates layout.html index.html login.html ...
因为导入任何目录下的文件,都会执行__init__.py中的顶层代码,而且在多层嵌套的情况下,引用会执行每层目录的__init__.py文件,因此建议将次文件留空。将单独module的程序转变为package结构程序的步骤如下:
- 将App的初始化以及参数的配置等功能放到application.py中。
- 在每个单独的功能module(例如views.py)通过
from application import app
对app进行引用,以使用app的接口。 - 创建一个顶层module(runserver.py)来执行application,在此module中先引入app初始module
import yourapplication.application
然后分别导入功能module例如import yourapplication.views
,最终执行applicationapp.run(debug=True)
。
在代码里面应该尽量避免循环引用,避免依赖循环。在上述步骤里面。modulerunserver依赖application以及各个功能module,各个功能module依赖于applicationmodule,形成不了依赖循环。当不得不面对循环依赖时,将其中的一个应用放到函数或者方法里面。
Blueprints
Flask支持blueprint将application可以分成几个部分,从接口功能上,blueprint对象和flask对象的类似。在Flask application中增加blueprint的支持,可以为较大或者复杂的application提供了一个新的组织结构方式:将程序中相似的部分功能放到一个blueprint对象中,然后将这些blueprints注册成到application。最终的application包含一个application对象(flask对象),所需要的extension的对象以及一系列blueprint对象。使用这种结构的优势:
- 将一个较大或者复杂的appliction转变为一系列相对独立的blueprints,便于维护。
- 每个blueprint在注册时对应一个URL前缀和subdomain,这样所有包含于blueprints中的view函数都以此前缀和subdomain作为参数。
- 可以将同一个blueprints,使用不同的URL规则进行注册。实现模块化复用代码。
- 对blueprints可以单独提供template filter,templates文件目录以及static文件目录。优先级比application的templates和static的优先级要低一些。
在使用blueprint的组织结构中,每个blueprint部分必须包含一个blueprint对象,以及这个部分的功能实现。blueprint对象的声明示例如下:
from flask import Blueprintbp = Blueprint('blueprint_user', __name__, template_folder='templates')
每个blueprint部分的实现简单点可以放在一个module中同样也可以和package结构相结合,放到一个目录结构中。和package结构结合的代码结构如下:
/yourapplication /runserver.py /yourapplication /__init__.py /application.py /views.py /static /style.css /templates layout.html index.html login.html /bpuser /__init__.py /blueprint_user.py /views.py /templates info.html /bpmanager /__init__.py /blueprint_manager.py ...
在示例代码中,bpuser以及bpmanager是两个blueprints的目录。其中在blueprint_user.py
以及blueprint_manager.py
进行了blueprint对象的声明。在application.py
中引用并注册blueprints。注册时示例如下:
from flask import Flaskimport bpuser.blueprint_userimport bpmanager.blueprint_managerapp = Flask(__name__)app.register_blueprint(blueprint_user.bp, url_prefix='/user')app.register_blueprint(blueprint_manager.bp, url_prefix='manager')
在application注册blueprint时,从根本实现上,application会记录blueprint的功能。然后application在功能触发时根据记录分发到相应的blueprint所在的模块进行处理。例如在blueprint_user.py中声明一个view endpoint。
@blueprint_user.route('/info')def info(): try: return render_template('info.html') except TemplateNotFound: abort(404)
然后application在注册blueprint时,将blueprint的功能记录在application中,在application中增加一些规则,在application使用时会根据相应规则发送到blueprint进行处理。这些记录的规则示例如下:
[<Rule '/user/info' (HEAD, OPTIONS, GET) ->blueprint_user.info>]
如规则中所示,在application使用blueprint所声明的endpoint(入口函数)都加了一个前缀,这个前缀就是blueprint的名字。因此在进行url转换时,使用url_for函数也必须要在endpoint前加上blueprint名字的前缀。如果转换函数在当前blueprint中使用,可以只在endpoint加一个点。
# in applicationurl_for(blueprint_user.info)# in blueprinturl_for(.info)
面向对象编程
如我们所知,面向对象的编程思想以及设计模式能够提供更好的代码结构,当前所描述的基于blueprint以及package的代码结构,虽然代码的实现在Module(源文件)以及函数中,但是依然符合了OOP(面向对象编程)的思想,同样也使用了相应的设计模式。也可以理解为当前结构是面向对象的结构。
- everthing in python is object. python中的一切都是对象,也可以按照处理对象的方式处理。比如函数、module、字符串、以及上述的package和blueprint都是对象。因此可以获取类型,可以作为参数传入函数,作为函数值,甚至包含属性和方法。因此这个结构是面向对象的。
- package以及blueprint的结构是使用目的,就是封装以及多态。这本身就符合面向对象的设计思想。blueprint的注册,decorator的使用本身也是设计模式的应用。因此这个结构是面向对象的。
但是当前结构中没有使用类(class),python的代码设计并不需要类来完成面向对象的设计,灵活的Module以及对于环境影响较小的函数是更推荐的方式。因此使用类需要基于以下原则:
- 如果需要将功能以及功能的状态绑定在一起,可以通过自定义类实现。将功能变为方法(method)将状态变为属性(property)。
- 如果功能会被多个线程使用,他所操作的资源在多线程环境下具备异步风险,因此不建议使用自定义类。
对于Flask程序,业务逻辑的实现,因为会同时发生很多个相同的请求,因此并不建议放到自定义类中。而用户界面在会有较多的功能重用,并且单一View会响应同一入口的不同的Http方法,因此可以放到自定义类中。对于用户界面的自定义类,Flask引入Pluggable view。
Pluggable View
通过Pluggable view,Flask通过自定义类为URL入口提供View。这种方式相比较于函数endpoint结构更清晰,同时提供了更多的灵活性。通过自定义类提供endpoint的简单示例如下:
from flask import View, render_templateclass ShowUsersView: def dispatch_request(self): users = User.query.all() return render_tempalte('show_users.html', objects = users)app.add_url_rule('/users/', view_func=ShowUsersView.as_view('show_users'))
自定义类提供VIEW的实现的必要因素有:
- 自定义类必须继承自View类(或者MethodView类)。
- 实现dispatch_request函数。
- 在app添加url处理规则时,通过as_view方法转变为一个endponit函数。其中参数就是endponit名称。
相比较于endpoint函数的方式,Pluggable view实现方式的优势包含:
- 通过一个类,对应同一入口可以定义不同的方法应对不同的http方法,结构更清晰。
- 通过类的继承和多态的特点将相似度很高的VIEW整合在一起,代码复用度较高切结构清晰。
Method View
自定义View继承MehtodView,可以在自定义View中响应不同的Htpp方法的请求。例如上述'/users/'的URL中可以对应的方法包含:
*URL* | *Method* | *Description* |
/users/ | GET | 获取所有的用户列表 |
/users/ | POST | 创建新用户 |
/users/id | GET | 获取单个用户 |
/users/id | PUT | 更新单个用户 |
/users/id | DELETE | 删除单个用户 |
通过继承自MethodView的自定义View,这些HTTP请求都可以在一个入口清晰的实现。实现示例:
class UsersView(MethodView): def get(self, user_id): if user_id is None: # return all users. pass else: # return a singal view pass def post(self): # create a new user pass def put(self, user_id): # update a user pass def delete(self, user_id): # delete a user. pass
然后逐条注册URL处理规则,同时可以将整个注册抽象出来作为函数在所有的自定义VIEW注册时使用。
def register_api(view, endpoint, url, pk='id', pk_type='int'): view_func = view.as_view(endpoint) app.add_url_rule(url, defaults={pk: None}, view_func=view_func, methods=['GET',]) app.add_url_rule(url, view_func=view_func, methods=['POST',]) app.add_url_rule('%s<%s:%s> % (url, pk, pk_type) view_func=view_func, methods=['GET', 'PUT', 'DELETE'])register_api(UsersView, 'users', '/users/', pk='user_id', pk_type='int')
View inherits
Pluggable view是自定义类,因此可以使用类的继承和多态的特性,将相似的VIEW整合在一起。例如:Users的VIEW是一个列表; Items的VIEW也是一个列表;因此可以继承自一个自定义的ListView。VIEW的继承关系可以和对应templates的继承关系一致,也也可以都使用父VIEW的template,非常灵活。
更进一步使用自定义类定义VIEW,可以将VIEW的实现已经VIEW的注册分发这种和业务逻辑有关的部分分离开来,将程序变成MVC的机构:VIEW <--> Model <--> 业务
当前使用这种结构需要符合应用的真实需要。代码结构设计的目的是更清晰的代码结构,更容易的代码阅读和维护。