Pyramid, Django, 和 Flask都是优秀的框架,为项目选择其中的哪一个都是伤脑筋的事。我们将会用三种框架实现相同功能的应用来更容易的对比三者。也可以直接跳到框架实战(Frameworks in Action)章节查看代码(code)。
1 简介
|
为了更容易在三者中作出选择(至少更了解它们),我们将用每一个框架构建同样的应用并比较它们的代码,对于每一个方法我们会高亮显示它的优点和缺点。如果你只想要代码,直接跳到框架实战章节(Frameworks in Action),或者查看其在Github上的代码。 Flask是一个面向简单需求小型应用的“微框架 (microframework)”。Pyramid和Django都是面向大型应用的,但是有不同的拓展性和灵活性。Pyramid的目的是更灵活,能 够让开发者为项目选择合适的工具。这意味着开发者能够选择数据库、URL结构、模板类型等等。Django目的是囊括web应用的所有内容,所以开发者只 需要打开箱子开始工作,将Django的模块拉进箱子中。 |
Django包括一个开箱即用的 ORM ,而Pyramid和 Flask让开发者自己选择如何或者是否存储他们的数据。到目前为止对于非Django的web应用来说最流行的ORM是SQLAlchemy,同时还有多种其他选择,从 DynamoDB和MongoDB 到简单本地存储的LevelDB 或朴实的SQLite。Pyramid被设计为可使用任何数据持久层,甚至是还没有开发出来的。
2、关于框架
|
前面为 Flask和Pyramid apps选择组件的额外工作给那些使用案例不适用标准ORM的开发者提供了更多的灵活性,同样也给使用不同工作流和模版化系统的开发者们带来了灵活性。 Flask,作为三个框架里面最稚气的一个,开始于2010年年中。Pyramid框架是从Pylons项 目开始的,在2010年底获得 Pyramid这个名字,虽然在2005年就已经发布了第一个版本。Django 2006年发布了第一个版本,就在Pylons项目(最后叫Pyramid)开始之后。Pyramid和Django都是非常成熟的框架,积累了众多插件 和扩展以满足难以置信的巨大需求。 虽然Flask历史相对更短,但它能够学习之前出现的框架并且把注意力放在了微小项目上。它大多数情况被使用在一些只有一两个功能的小型项目上。例如 httpbin,一个简单的(但很强大的)调试和测试HTTP库的项目。 |
3. 社区
|
4. Bootstrapping
|
1
2
3
4
5
6
7
8
9
10
|
# from http://flask.pocoo.org/ tutorial
from flask import Flask
app = Flask(__name__)
@app .route( "/" ) # take note of this decorator syntax, it's a common pattern
def hello():
return "Hello World!"
if __name__ = = "__main__" :
app.run()
|
这是Flask没有bootstrapping工具的原因:没有它们的需求。从Flask主页上的Hello World特性看,没有构建Python web应用经验的开发者可以立即开始hacking。
对于各部分需要更多分离的项目,Flask有blueprints。例如,你可以将所有用户相关的函数放在users.py中,将销售相关的函数放 在ecommerce.py中,然后在site.py中添加引用它们来结构化你的Flask应用。我们不会深入这个功能,因为它超出了我们展示demo应 用的需求。
4.2 Pyramid
|
1
|
$ pcreate - s starter hello_pyramid # Just make a Pyramid project
|
Pyramid 比 Flask 适用于更大更复杂的应用程序. 因为这一点,它的 bootstrapping工具创建更大的项目骨架. Pyramid 同样加入了基本的配置文件,一个例子模版和用于将程序打包上传到 Python Package Index的所有文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
hello_pyramid
├── CHANGES.txt
├── development.ini
├── MANIFEST. in
├── production.ini
├── hello_pyramid
│ ├── __init__.py
│ ├── static
│ │ ├── pyramid - 16x16 .png
│ │ ├── pyramid.png
│ │ ├── theme.css
│ │ └── theme. min .css
│ ├── templates
│ │ └── mytemplate.pt
│ ├── tests.py
│ └── views.py
├── README.txt
└── setup.py
|
作为最后描述的框架,Pyramid的bootstrapper非常灵活. 不局限于一个默认的程序;pcreate 可以使用任意数量的项目模版. 包括我们上面用到的pcreate里面的"starter"的模版, 还有 SQLAlchemy- ,ZODB-支持scaffold项目. 在 PyPi可以发现已经为Google App Engine, jQuery Mobile, Jinja2 templating, modern frontend frameworks做好的scaffolds, 还有更多~
4.3 Django
|
1
2
|
django - admin startproject hello_django
django - admin startapp howdy # make an application within our project
|
Django 跟 Pyramid 区别在于: Django 由多个应用程序组成一个项目, 而 Pyramid 以及 Flask 项目是包含 View 和 Model 单一应用程序 . 理论上, Flask 和 Pyramid 的项目允许存在多个 project/app, 不过在默认配置中只能有一个.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
hello_django
├── hello_django
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── howdy
│ ├── admin.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
└── manage.py
|
Django 默认只在项目中创建 空白的 model 和模板文件, 供新手参考的示范代码不多. 此外, 开发者在发布应用程序的时候, 还要自己配置, 这也是个麻烦.
bootstrap 工具的缺点是没有指导开发者如何打包应用. 对于那些没有经验的新手来说, 第一次部署应用将是个很头疼的问题. 像 django-oscar 这样的大社区, 项目都是打包好了, 放在 PyPi 上供大家安装. 但是 Github 上面的小项目缺少统一的打包方式.
5 模板
|
5.1 Django
|
1
2
3
4
5
6
7
|
def a_view(request):
# get the logged in user
# ... do more things
return render_to_response(
"view.html" ,
{ "user" : cur_user}
)
|
拥有这个模板的上下文很简单,传入一个Python对象的字典和模板使用的数据结构。现在我们需要在页面上渲染他们的名字,以防页面忘了他们是谁。
1
2
3
4
5
6
7
8
9
10
11
|
<!-- view.html -->
< div class = "top-bar row" >
< div class = "col-md-10" >
<!-- more top bar things go here -->
</ div >
{{9712d1b0ae091f3a4a596b93bd6b9ff479ef0818eaa8cb96fef39cc945859a28} if user {9712d1b0ae091f3a4a596b93bd6b9ff479ef0818eaa8cb96fef39cc945859a28}}
< div class = "col-md-2 whoami" >
You are logged in as {{ user.fullname }}
</ div >
{{9712d1b0ae091f3a4a596b93bd6b9ff479ef0818eaa8cb96fef39cc945859a28} endif {9712d1b0ae091f3a4a596b93bd6b9ff479ef0818eaa8cb96fef39cc945859a28}}
</ div >
|
首先,你会注意到这个 {{9712d1b0ae091f3a4a596b93bd6b9ff479ef0818eaa8cb96fef39cc945859a28} if user {9712d1b0ae091f3a4a596b93bd6b9ff479ef0818eaa8cb96fef39cc945859a28}} 概念。在Django模板中, {{9712d1b0ae091f3a4a596b93bd6b9ff479ef0818eaa8cb96fef39cc945859a28} 用来控制循环和条件的声明。这里的if user声明是为了防止那些不是用户的情况。匿名用户不应该在页面头部看到“你已经登录”的字样。
在if块内,你可以看到,包含名字非常的简单,只要用{{}}包含着我们要插入的属性就可以了。{{是用来向模板插入真实值的,如{{ user.fullname }}。
模板的另一个常用情况是展示一组物品,如一个电子商务网站的存货清单页面。
1
2
3
4
5
6
|
def browse_shop(request):
# get items
return render_to_response(
"browse.html" ,
{ "inventory" : all_items}
)
|
在模板中,我们使用同样的{{9712d1b0ae091f3a4a596b93bd6b9ff479ef0818eaa8cb96fef39cc945859a28}来循环清单中的所有条目,并填入它们各自的页面地址。
1
2
3
|
{ {9712d1b0ae091f3a4a596b93bd6b9ff479ef0818eaa8cb96fef39cc945859a28} for widget in inventory {9712d1b0ae091f3a4a596b93bd6b9ff479ef0818eaa8cb96fef39cc945859a28} }
<li><a href = "/widget/{{ widget.slug }}/" >{{ widget.displayname }}< / a>< / li>
{ {9712d1b0ae091f3a4a596b93bd6b9ff479ef0818eaa8cb96fef39cc945859a28} endfor {9712d1b0ae091f3a4a596b93bd6b9ff479ef0818eaa8cb96fef39cc945859a28} }
|
为了做大部分常见的模板任务,Django可以仅仅使用很少的结构来完成目标,因此很容易上手。
5.2 Flask
|
1
2
3
4
5
|
<!-- Django -->
< div class = "categories" >Categories: {{ post.categories|join:", " }}</ div >
<!-- now in Jinja -->
< div class = "categories" >Categories: {{ post.categories|join(", ") }}</ div >
|
在Jinja模板语言中,可以向过滤器传入任意数量的参数,因为Jinja把它看成是 使用括号包含参数的Python函数的一个调用。Django使用冒号来分割过滤器的名字和过滤参数,这限制了参数的数目只能为一。
Jinjia和Django的for循环有点类似。我们来看看他们的不同。在Jinjia2中,for-else-endfor结构能遍历一个列表,同时也处理了没有项的情况。
Django版的这个功能是一样的,但是是用for-empty-endfor而不是for-else-endfor。
除了语法上的不同,Jinja2通过执行环境和高级特性提供了更多的控制。例如,它可以关闭危险的特性以安全的执行不受信任的模板,或者提前编译模板以确保它们的合法性。 |
5.3 Pyramid
|
1
2
3
4
|
@view_config (renderer = 'templates/home.pt' )
def my_view(request):
# do stuff...
return { 'user' : user}
|
但是我们的模板看起来有些不同。ZPT是一个基于XML得模板标准,所以我们使用了类XSLT语句来操作数据。
1
2
3
4
5
6
7
8
9
|
< div class = "top-bar row" >
< div class = "col-md-10" >
<!-- more top bar things go here -->
</ div >
< div tal:condition = "user"
tal:content = "string:You are logged in as ${user.fullname}"
class = "col-md-2 whoami" >
</ div >
</ div >
|
Chameleon对于模板操作有三种不同的命名空间。TAL(模板属性语言)提供了基本的条件语句,字符串的格式化,以及填充标签内容。上面的例 子只用了TAL来完成相关工作。对于更多高级任务,就需要TALES和METAL。TALES( 模板属性表达式语法的语言)提供了像高级字符串格式化,Python表达式评估,以及导入表达式和模板的表达式。
METAL(宏扩展模板属性语言)是Chameleon模板最强大的(和复杂的)一部分。宏是可扩展的,并能被定义为带有槽且当宏被调用时可以被填充。
6. 利用框架行动起来
|
1
2
3
4
5
6
7
8
9
10
11
|
from flask import Flask
# For this example we'll use SQLAlchemy, a popular ORM that supports a
# variety of backends including SQLite, MySQL, and PostgreSQL
from flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
# We'll just use SQLite here so we don't need an external database
app.config[ 'SQLALCHEMY_DATABASE_URI' ] = 'sqlite:///test.db'
db = SQLAlchemy(app)
|
现在我们看下我们的模型,这将和另两个样例基本一样。
1
2
3
4
5
|
class Lunch(db.Model):
"""A single lunch"""
id = db.Column(db.Integer, primary_key = True )
submitter = db.Column(db.String( 63 ))
food = db.Column(db.String( 255 ))
|
哇,相当简单。最难的部分是找到合适的 SQLAlchemy数据类型,选择数据库中String域的长度。使用我们的模型也超级简单,这在于我们将要看到 SQLAlchemy查询语法。
构建我们的提交表单也很简单。在引入Flask-WTForms和正确的域类型后,你可以看到表单看起来有点像我们的模型。主要的区别在于新的提交按钮和食物与提交者姓名域的提示。 应用中的SECRET_KEY域是被WTForms用来创建CSRF符号的。它也被itsdangerous(Flask内包含)用来设置cookies和其他数据。
让表单在浏览器中显示意味着模板要有它。我们像下面那样传递进去。
|
好了,发生了什么?我们得到已经用Lunch.query.all()提交的午餐列表,并实例化一个表单,让用户提交他们自己的美食之旅。为了简化,变量使用相同的名字出入模板,但这不是必须的。
这就是模板的真实情况,我们在已经吃过的午餐中循环,并在<ul>中展示他们。这几乎与我们前面看到的循环例子一样。
模板的<form>部分仅仅渲染我们在root()视图中传入模板的WTForm对象的表单标签和输入。当表单提交时,它将向/new提交一个POST请求,这个请求会被下面的函数处理。
在验证了表单数据后,我们把内容放入我们Model对象中,并提交到数据库。一旦我们在数据库中存了午餐,它将在人们吃过的午餐列表中出现。
最后,我们只需做(非常)少量的工作来让应用运行起来。使用SQLAlchemy,我们可以创建存储午餐的表,然后开始运行我们写的路径管理就行了。 |
6.2 测试Django版APP
|
1
2
3
4
5
6
|
# from wut4lunch/models.py
from django.db import models
class Lunch(models.Model):
submitter = models.CharField(max_length = 63 )
food = models.CharField(max_length = 255 )
|
在表单系统上。不像Flask,我们可以用Django内建的表单系统。它看起来非常像我们在Flask中使用的WTFroms模块,只是语法有点不同。
1
2
3
4
5
6
7
8
9
10
11
12
|
from django import forms
from django.http import HttpResponse
from django.shortcuts import render, redirect
from .models import Lunch
# Create your views here.
class LunchForm(forms.Form):
"""Form object. Looks a lot like the WTForms Flask example"""
submitter = forms.CharField(label = 'Your name' )
food = forms.CharField(label = 'What did you eat?' )
|
现在我们只需要构造一个LunchForm实例传递到我们的模板。
1
2
3
4
5
6
7
8
9
10
11
12
|
lunch_form = LunchForm(auto_id = False )
def index(request):
lunches = Lunch.objects. all ()
return render(
request,
'wut4lunch/index.html' ,
{
'lunches' : lunches,
'form' : lunch_form,
}
)
|
render函数是Django shortcut,以接受请求、模板路径和一个上下文的dict。与Flask的render_template类似,它也接受接入请求。
1
2
3
4
5
6
|
def newlunch(request):
l = Lunch()
l.submitter = request.POST[ 'submitter' ]
l.food = request.POST[ 'food' ]
l.save()
return redirect( 'home' )
|
保存表单应答到数据库是不一样的,Django调用模型的 .save()方法以及处理会话管理而不是用全局数据库会话。干净利落!
Django 提供了一些优雅的特性,让我们管理用户提交的午餐,因此我们可以删除那些不合适的午餐信息。Flask和Pyramid没有自动提供这些功能,而在创建一 个Django应用时不需要写另一个管理页面当然也是其一个特性。开发者的时间可不免费啊!我们所要做的就是告诉Django-admin我们的模型,是 在wut5lunch/admin.py中添加两行。
Bam。现在我们可以添加删除一些条目,而无需额外的工作。 最后,让我们看下主页模板的不同之处。
Django拥有方便的快捷方式,在你的页面中引用其他的视图。url标签可以使你重建应用中的URLs,而不需破坏视图。这个是因为url标签会主动查询视图中的URL。
表单被不同的语法渲染,我们需要人工在表单主体中添加CSRF token,但这些区别更多的是装饰 |
6.3测试Pyramid版App
|
1
2
3
4
5
6
7
8
9
10
11
|
<!-- pyramid_wut4lunch/templates/index.pt -->
< div tal:condition = "lunches" >
< ul >
< div tal:repeat = "lunch lunches" tal:omit-tag = "" >
< li tal:content = "string:${lunch.submitter} just ate ${lunch.food}" />
</ div >
</ ul >
</ div >
< div tal:condition = "not:lunches" >
< em >Nobody has eaten lunch, you must all be starving!</ em >
</ div >
|
与Django模板类似,缺少for-else-endfor结构使得逻辑稍微的更清晰了。这种情况下,我们以if-for 和 if-not-for 语句块结尾以提供同样的功能。使用{{或{{9712d1b0ae091f3a4a596b93bd6b9ff479ef0818eaa8cb96fef39cc945859a28}来控制结构和条件的Django以及AngularJS类型的模板让使用XHTML标签的模板显得很外行。
Chameleon模板类型的一大好处是你所选择的编辑器可以正确的使语法高亮,因为模板是有些得XHTML。对于Django和Flask模板来说,你的编辑器需要能够正确的支持这些模板语言高亮显示。
Pyramid中表单得转换稍微更细致些,因为pytamid_simpleform不像Django表单的form.as_ul函数那样可以自动转换所有的表单字段。 现在我们看看什么返回给应用。首先,定义我们需要得表单并呈现我们的主页。
|
获取午餐的查询语法和Flask的很相似,这是因为这两个demo应用使用了流行的SQLAlchemy ORM来 提供持久存储。在Pyramid中,允许你直接返回模板上下文的字典,而不是要调用特殊的render函数。@view_config装饰器自动将返回的 上下文传入要渲染的模板。避免调用render方法使得Pyramid写的函数更加容易测试,因为它们返回的数据没有被模板渲染对象掩盖。
从Pyramid的请求对象中更加容易得到表单数据,因为在我们获取时会自动将表单POST数据解析成dict。为了阻止同一时间多并发的请求数据 库,ZopeTransactions模块提供了上下文管理器,对写入逻辑事物的数据库进行分组,并阻止应用的线程在各个改变时互相影响,这在你的视图共 享一个全局session并接收到大量通信的情况下将会是个问题。 |
7. 总结
|
![]()
Ley
|
Flask对于那些开发小项目、需要快速制作一个简单的Python支撑的网站的开发者很有用。它提供小型的统一工具,或者在已有的API上构建的简单网络接口。可以快速开发需要简单web接口并不怎么配置的后端项目使用Flask将会在前端获益,如jitviewer提供了一个web接口来检测PyPy just-in-time的编译日志。 这三个框架都能解决我们简单的需求,我们已经看到了它们的不同。这些区别不仅仅是装饰性的,它们将会改变你设计产品的方法,以及添加新特性和修复的 速度。因为我们的例子很小,我们看到Flask的闪光点,以及Django在小规模应用上的笨重。Pyramid的灵活并未体现出来,因为我们的要求是一 样的,但在真实场景中,新的需求会常常出现。 7.1 致谢 标题图像的logo来自与Flask、Django和Pyramid项目网站。 这篇文章非常感谢它的评阅者,Remy DeCausemaker,Ross Delinger和Liam Middlebrook,忍受了许多初期的草稿。 这篇文章的当前样式来自于Adam Chainz、bendwarn、Serger Maertens、Tom Leo和wichert的评论和修正(名字按字母表顺序)。 |