Django 、 Flask 、 Pyramid框架对比

1                         前言

Pyramid,Django和Flask都是非常不错的Web框架,如何为你的项目从中选择最合适的是一个问题。本文中,会使用这三个Web框架来实现具备同一个功能的网站,以此来进行对比。

2                        简介

PythonWeb框架的世界里总是充满着选择,Django,Flask,Pyramid,Tornado,Bottle,Diesel,Pecan,Falcon和其他各式各样的框架摆在开发者的眼前。作为一个开发者,你需要能够从中选出一款适合你的能帮助你完成项目的框架。本文中,我们把注意力集中到Flask,Pyramid和Django上。

我们将使用这三款框架去构建同样一个应用并且对比代码,体现出每种框架的优势和劣势。如果你想直接看代码,请点击https://github.com/ryansb/wut4lunch_demos

Flask是一个“微型”框架,主要关注一些简单的应用。Pyramid和Django都是关注大型应用的框架,但是使用不同的方法来达到不同的扩展性和灵活性。Pyramid关注灵活性,让开发者为自己的项目自主选择合适的工具。这意味着开发者可以选择数据库、URL架构、模板风格等等。Django注重将一个web应用所需的所有模块都包括进来,开发者只需要直接使用该框架进行工作即可。

Django默认自带ORM,而Pyramid和Flask让开发者自行去选择如何存储数据。非Django的web应用最常使用的ORM是SQLAlchemy。当然还有其他众多的选择,如DynamoDB和MongoDB、LevelDB、SQLite等。

3                         关于框架

 

Django自带所有所需模块的模式使得开发者能够非常容易地直接关注web应用开发本身,而不需要去做很多关于应用架构如何设计的决定。Django内嵌了模板、表单、路由、认证、基本数据库管理等模块。Pyramid包括路由和认证,但是模板和数据库管理则需要额外的库。

Flask,三个框架中最年轻的一个,始于2010年中。Pyramid框架出自Pylons,在2010年末得名,最早的一个版本是在2005年。Django在2006年发布第一个版本,就在Pylons刚开始的时候。Pyramid和Django都是非常成熟的框架,有相当多的插件和扩展模块来满足各类需求。

虽然Flask更年轻一些,但其更有机会去学习之前的框架并把自己的关注点放到了小项目上。其经常被只有一两个功能的小项目使用,比如httpbin,http://httpbin.org/,一个简单但是强力的调试和测试HTTP的库。

 

4                         社区

 从StackOverflow上各个框架相关的问题数量就可以看出哪个框架更加受欢迎了。

5                         引导程序

Django和Pyramid都内嵌各自的引导程序。Flask并未内嵌的原因是其使用者并不会使用Flask去开发大型的MVC应用。

5.1                     Flask

7行代码就可以组成一个基于Flask的Hello World应用。

[python]  view plain  copy
  1. # from http://flask.pocoo.org/ tutorial  
  2. from flask import Flask  
  3. app = Flask(__name__)  
  4.  
  5. @app.route("/"# take note of this decorator syntax, it's a common pattern  
  6. def hello():  
  7.     return "Hello World!"  
  8.   
  9. if __name__ == "__main__":  
  10.     app.run()  

这就是Flask没有内嵌引导程序的原因了:没有这样的需求。

基于上面这个例子,这是一个已经可以运行的基于Flask的网站了。因此,甚至一个没有开发过Python Web应用的开发者都可以直接开始开发了。

对于那些不同功能组件 需要分割的项目,Flask有blueprints机制。例如,你可以将所有用户相关的功能放在users.py文件中,所有销售相关的功能放在ecommerce.py中,然后将他们导入到site.py中。这里我们不再举例说明了,超出了这个实例程序讨论的范围。

5.2                     Pyramid

Pyramid的引导程序叫做pcreate,是Pyramid的组成部分。

[python]  view plain  copy
  1. $ pcreate-s starter hello_pyramid # Just make a Pyramid project  

Pyramid 通常被用来开发比Flask更大型的应用。因此,pcreate创建的项目有着更多的内容,包括基本的配置文件、模板示例和需要将你的项目上传到Python Package Index所需要的文件。

[html]  view plain  copy
  1. hello_pyramid  
  2. ├── CHANGES.txt  
  3. ├── development.ini  
  4. ├── MANIFEST.in  
  5. ├── production.ini  
  6. ├── hello_pyramid  
  7. │   ├── __init__.py  
  8. │   ├── static  
  9. │   │   ├── pyramid-16x16.png  
  10. │   │   ├── pyramid.png  
  11. │   │   ├── theme.css  
  12. │   │   └── theme.min.css  
  13. │   ├── templates  
  14. │   │   └── mytemplate.pt  
  15. │   ├── tests.py  
  16. │   └── views.py  
  17. ├── README.txt  
  18. └── setup.py  

5.3                     Django

 

Django也有自带的引导程序———django-admin。

[python]  view plain  copy
  1. django-admin startproject hello_django  
  2. django-admin startapp howdy # make an application within our project  

我们已经可以明显地看出Django和Pyramid的不同之处了。Django将不同的项目划分到不同的应用中去。而Flask和Pyramid会多个应用放到一个项目中去,通过不同的view来进行区分。当然,开发者手动地进行区分也是可行的,但是默认并不进行区分。

[html]  view plain  copy
  1. hello_django  
  2. ├── hello_django  
  3. │   ├── __init__.py  
  4. │   ├── settings.py  
  5. │   ├── urls.py  
  6. │   └── wsgi.py  
  7. ├── howdy  
  8. │   ├── admin.py  
  9. │   ├── __init__.py  
  10. │   ├── migrations  
  11. │   │   └── __init__.py  
  12. │   ├── models.py  
  13. │   ├── tests.py  
  14. │   └── views.py  
  15. └── manage.py  

Django默认只提供空的model和模板文件,新用户只能看到一些简单的示例代码。

引导程序不引导用户去打包他们的应用,这种做法有一个不足之处,是新手没有这个意识去这样做。如果一个开发者之前没有打包过应用,那么他会发现他第一次部署的时候会有多么的困难。但基本上小型的项目都没有统一打包。

 

6                         模板

 

有了基本的HTTP 服务器还远远不够,用户肯定希望你还能有个风光靓丽的界面。

模板让你能够动态地更改页面信息,而不要调用AJAX。这个机制从用户的角度来说非常的友好,因为你只需要取一次全页面的信息和其他动态的数据,对于一些移动设备访问网站来说,这样的行为能为他们节省许多时间。

所有的模板都基于context,提供动态的信息给模板并最终渲染到HTML中。让我们来看一个例子,将用户的登录名显示给他们看。

6.1                     Django

我们的示例非常简单,假设我们有一个user对象,其有一个fullname属性,包含了user的名字。我们会这样传递给模板:

[python]  view plain  copy
  1. def a_view(request):  
  2.     # get the logged in user  
  3.     # ... do more things  
  4.     return render_to_response(  
  5.         "view.html",  
  6.         {"user": cur_user}  
  7.     )  

接下来我们需要将user对象传入到模板中去。可以在其中直接访问其fullname属性。

[html]  view plain  copy
  1. <!-- view.html -->  
  2. <div class="top-bar row">  
  3.   <div class="col-md-10">  
  4.   <!-- more top bar things go here -->  
  5.   </div>  
  6.   {% if user %}  
  7.   <div class="col-md-2 whoami">  
  8.     You are logged in as {{ user.fullname }}  
  9.   </div>  
  10.   {% endif %}  
  11. </div>  

首先,你会发现其中{%  if user %}这个结构。在Django中{%是用来放循环或条件控制等语句的。if user用来判断是否有user传入。匿名user是无法看到’You are logged in as xxx’的页面的。

在if代码块中,只需要简单地把对象及其属性放到{{  }}中去即可,模板就会自动去调用实际的值。

 

另一个模板的常见用法是显示成组的信息,比如商业网站的库存信息:

[html]  view plain  copy
  1. def browse_shop(request):  
  2.     # get items  
  3.     return render_to_response(  
  4.         "browse.html",  
  5.         {"inventory": all_items}  
  6.     )  

其中,我们仍然可以使用{%来循环遍历仓库中所有的项目并且填充到各自的页面中,如:

[html]  view plain  copy
  1. {% for widget in inventory %}  
  2.     <li><a href="/widget/{{ widget.slug }}/">{{ widget.displayname }}</a></li>  
  3. {% endfor %}  

对于大部分的需求来说,Django的模板系统可以通过非常简单的结构来满足。

 

6.2                     Flask

 

Flask默认使用Django推荐的Jinja2模板语言,但是也可以配置成使用其他的语言。实际上Jinja2和Django的模板系统还是有一些不同之处的,Jinja2更加易读。

Jinja2和Django的模板系统偶读提供了filtering功能。一个list在被显示之前可以被一个指定的函数进行处理。例如:

[html]  view plain  copy
  1. <!-- Django -->  
  2. <div class="categories">Categories: {{ post.categories|join:", " }}</div>  
  3.   
  4. <!-- now in Jinja -->  
  5. <div class="categories">Categories: {{ post.categories|join(", ") }}</div>  

Jinja2的模板语言中,能够传递任意数量的参数给filter,因为Jinja2进行的是函数的调用,将参数传递给filter之后的函数。Django使用一个冒号作为filter和其参数的分隔符,这导致了参数的数量被限制在了1个,因为只能有一个冒号,冒号后面的内容都被认作为是参数。

Jinja2和Django的模板系统对于for循环都是类似的,让我们来看看他们的区别在哪里。在Jinja2中,附带了for-else-endfor架构,让你能够遍历list,并且在没有内容的情况下进入else进行处理。

[html]  view plain  copy
  1. {% for item in inventory %}  
  2. <div class="display-item">{{ item.render() }}</div>  
  3. {% else %}  
  4. <div class="display-warn">  
  5. <h3>No items found</h3>  
  6. <p>Try another search, maybe?</p>  
  7. </div>  
  8. {% endfor %}  

Django也有类似的结构,但是关键字是empty。如:

[html]  view plain  copy
  1. {% for item in inventory %}  
  2. <div class="display-item">{{ item.render }}</div>  
  3. {% empty %}  
  4. <div class="display-warn">  
  5. <h3>No items found</h3>  
  6. <p>Try another search, maybe?</p>  
  7. </div>  
  8. {% endfor %}  

除了上面提到的各类区别,Jinja2提供了更多的控制或功能。比如其安全性和提前编译模板以避免语法错误等功能,使其面对Django更优一筹。

6.3                     Pyramid

 

Pyramid也支持很多模板语言(如Jinja2或Mako),但是其使用Chameleon作为默认的模板系统。我们来看看前面提到的显示username的例子在Pyramid里是如何实现的,Python 代码看上去比较类似:

[python]  view plain  copy
  1. @view_config(renderer='templates/home.pt')  
  2. def my_view(request):  
  3.     # do stuff...  
  4.     return {'user': user}  

但是模板的写法就非常不一样了。ZPT(Zope Page Template)是一个基于XML的模板标准,所有我们使用XSLT类似的语法:

[html]  view plain  copy
  1. <div class="top-bar row">  
  2.   <div class="col-md-10">  
  3.   <!-- more top bar things go here -->  
  4.   </div>  
  5.   <div tal:condition="user"  
  6.        tal:content="string:You are logged in as ${user.fullname}"  
  7.        class="col-md-2 whoami">  
  8.   </div>  
  9. </div>  

Chameleon实际上使用三个不同的命名空间来进行模板内的操作。TAL(template attribute language)提供了基本的条件判断、字符串格式化和填充tag内容等操作。上面的例子就是使用了tal来进行填充。对于更高级的用法,我们就需要使用到TALES和METAL。TALES(Template Attribute Language Expression Syntax)提供了高级字符串处理的表达式和Python表达式求值、表达式导入和模板等。

METAL(Macro Expansion Template Attribute Language)是最强大也是最复杂的部分。其是可扩展的。

7                         各个框架的实例展示

 

下面我们来创建一个APP叫做wut4lunch,用来告诉别人你午饭吃了什么。这个APP允许用户上传他们午餐吃了什么,然后浏览别人吃的什么,预期主页面是这个样子的:

 

7.1                     Flask

 代码非常简单,主要包括初始化APP和从ORM获取信息。

[python]  view plain  copy
  1. from flask import Flask   
  2. # For this example we'll use SQLAlchemy, a popular ORM that supports a # variety of backends including SQLite, MySQL, and PostgreSQL   
  3. from flask.ext.sqlalchemy import SQLAlchemy   
  4. app = Flask(__name__)   
  5. # We'll just use SQLite here so we don't need an external database app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'   
  6. db = SQLAlchemy(app)  

接下来是model模块,基本上类似

[python]  view plain  copy
  1. class Lunch(db.Model):   
  2.     """A single lunch"""   
  3.     id = db.Column(db.Integer, primary_key=True)   
  4.     submitter = db.Column(db.String(63))   
  5.     food = db.Column(db.String(255))  

是不是很简单?最困难的部分就是找到正确的SQLAlchemy data类型和长度。

构建提交的表单也是很简单的。导入Flask-WTForms和正确的field类型,你就会发现表单看上去非常像我们的model,主要的区别一个新的提交按钮和输入框的命名。

其中SECRET_KEY这个字段被WTForms用来建立CSRF令牌等其他用途。

[python]  view plain  copy
  1. from flask.ext.wtf import Form   
  2. from wtforms.fields import StringField, SubmitField   
  3. app.config['SECRET_KEY'] = 'please, tell nobody'   
  4.   
  5. class LunchForm(Form):   
  6.     submitter = StringField(u'Hi, my name is')   
  7.     food = StringField(u'and I ate')   
  8.     # submit button will read "share my lunch!"   
  9.     submit = SubmitField(u'share my lunch!')  
  10.       

接下来在模板中把表单加进去:

[python]  view plain  copy
  1. from flask import render_template   
  2.  
  3. @app.route("/")   
  4. def root():   
  5.     lunches = Lunch.query.all()   
  6.     form = LunchForm()   
  7.     return render_template('index.html', form=form, lunches=lunches)  

 
 

在这段代码中,我们通过Lunch.query.all()获取到所有已经上传的午餐信息,并且初始化了一个表单供用户上传他们自己的数据。接下来是模板的内容:

首先是开头的介绍:

[html]  view plain  copy
  1. <html>   
  2. <title>Wut 4 Lunch</title>   
  3. <b>What are people eating?</b>   
  4. <p>Wut4Lunch is the latest social network where you can tell all your friends about your noontime repast!</p>  


接着是最重要的部分,将我们查询的结果通过循环遍历出来,全部呈现到页面上:

 

[html]  view plain  copy
  1. <li><strong>{{ lunch.submitter|safe }}</strong> just ate <strong>{{ lunch.food|safe }}</strong>  
  2. {% else %}   
  3. <li><em>Nobody has eaten lunch, you must all be starving!</em></li>   
  4. {% endfor %}   
  5. </ul>   
  6. <b>What are YOU eating?</b>   
  7. <form method="POST" action="/new">   
  8.     {{ form.hidden_tag() }}   
  9.     {{ form.submitter.label }} {{ form.submitter(size=40) }}   
  10.     <br/>   
  11.     {{ form.food.label }} {{ form.food(size=50) }}   
  12.     <br/>   
  13.     {{ form.submit }}   
  14. </form>   
  15. </html>  

<form>中明显可以看出,当用户POST一个表单的时候,会转入/new指明的模块进行处理:

[python]  view plain  copy
  1. from flask import url_for, redirect   
  2. @app.route(u'/new', methods=[u'POST'])   
  3. def newlunch():   
  4.     form = LunchForm()   
  5.     if form.validate_on_submit():   
  6.         lunch = Lunch()   
  7.         form.populate_obj(lunch)   
  8.         db.session.add(lunch)   
  9.         db.session.commit()   
  10.     return redirect(url_for('root'))  

确认提交上来的数据正确无误之后,就会将其存储到数据库中。以供后续操作查阅。

[python]  view plain  copy
  1. if __name__ == "__main__":   
  2.     db.create_all()   
  3.     # make our sqlalchemy tables   
  4.     app.run()  
最终我们建立数据库并启动我们的app。 代码量非常的少!! 

7.2                     Django

 

Django的版本和Flask版本非常类似,但是其分散在不同的文件中。让我们先看最类似的部分:数据库的model。

[python]  view plain  copy
  1. # from wut4lunch/models.py   
  2. from django.db import models   
  3. class Lunch(models.Model):   
  4.     submitter = models.CharField(max_length=63)   
  5.     food = models.CharField(max_length=255)  


对于表单系统,Django有自己的一套,和Flask的比起来,只是语法上略有不同。

[python]  view plain  copy
  1. from django import forms   
  2. from django.http import HttpResponse   
  3. from django.shortcuts import render, redirect   
  4. from .models import Lunch   
  5.   
  6. # Create your views here.   
  7. class LunchForm(forms.Form):   
  8.     """Form object. Looks a lot like the WTForms Flask example"""   
  9.     submitter = forms.CharField(label='Your name')   
  10.     food = forms.CharField(label='What did you eat?')  

接下来我们需要创建一个LunchForm的实例并将其传递给模板。

[python]  view plain  copy
  1. lunch_form = LunchForm(auto_id=False)  
  2.   
  3. def index(request):  
  4.     lunches = Lunch.objects.all()  
  5.     return render(  
  6.         request,  
  7.         'wut4lunch/index.html',  
  8.         {  
  9.             'lunches': lunches,  
  10.             'form': lunch_form,  
  11.         }  
  12.     )  

render函数是Django中用来处理request、模板路径和内容的,类似于Flask中的render_template。

[python]  view plain  copy
  1. def newlunch(request):   
  2.     l = Lunch()   
  3.     l.submitter = request.POST['submitter']   
  4.     l.food = request.POST['food']   
  5.     l.save()   
  6.     return redirect('home')  

将用户上传的数据保存到数据库,Django并未采用直接调用数据库session的方法,而是使用了框架自带的.save()方法。非常的整洁!


我们还需要告诉Django-admin我们的model在哪里:

[python]  view plain  copy
  1. from wut4lunch.models import Lunch  
  2. admin.site.register(Lunch)  

接下来是我们的页面信息:

[html]  view plain  copy
  1. <ul>   
  2. {% for lunch in lunches %}   
  3. <li><strong>{{ lunch.submitter }}</strong> just ate <strong>{{ lunch.food }}</strong></li>   
  4. {% empty %}   
  5. <em>Nobody has eaten lunch, you must all be starving!</em>   
  6. {% endfor %}   
  7. </ul>  

值得一提的是,Django有一个非常方便的途径在页面中加入其他的视图。url标签让你能够定义APP的URL而不需要破坏视图。例如:

[html]  view plain  copy
  1. <form action="{% url 'newlunch' %}" method="post">   
  2.     {% csrf_token %}   
  3.     {{ form.as_ul }}   
  4.     <input type="submit" value="I ate this!" />   
  5. </form>  

然后我们还需要包含{%  csrf_token%}来确保CSRF正常。但这些都是小问题。

 

7.3                     Pyramid

 

最后让我们看一看同样的功能,用Pyramid是如何实现的。和前两个最大的区别在于模板。Chameleon模板的语法严格遵循XSLT。

[html]  view plain  copy
  1. <!-- pyramid_wut4lunch/templates/index.pt -->   
  2. <div tal:condition="lunches">   
  3.     <ul>   
  4.         <div tal:repeat="lunch lunches" tal:omit-tag="">   
  5.             <li tal:content="string:${lunch.submitter} just ate ${lunch.food}"/>   
  6.         </div>   
  7.     </ul>   
  8. </div>   
  9. <div tal:condition="not:lunches">   
  10.     <em>Nobody has eaten lunch, you must all be starving!</em>   
  11. </div>  

表单是这样的:

[html]  view plain  copy
  1. <pre name="code" class="html"><b>What are YOU eating?</b>   
  2. <form method="POST" action="/newlunch">   
  3.     Name: ${form.text("submitter", size=40)}   
  4.     <br/>   
  5.     What did you eat? ${form.text("food", size=40)}   
  6.     <br/>   
  7.     <input type="submit" value="I ate this!" />   
  8. </form>   
  9. </html>  

 
 

关于表单的渲染也比Django要复杂一些。首先我们需要定义表单,同时渲染主页:

[python]  view plain  copy
  1. # pyramid_wut4lunch/views.py   
  2.   
  3. class LunchSchema(Schema):   
  4.     submitter = validators.UnicodeString()   
  5.     food = validators.UnicodeString()   
  6.      
  7. @view_config(route_name='home', renderer='templates/index.pt')   
  8.   
  9. def home(request):   
  10.     lunches = DBSession.query(Lunch).all()   
  11.     form = Form(request, schema=LunchSchema())   
  12.     return {'lunches': lunches, 'form': FormRenderer(form)}  

数据库查询语句的语法和Flask都是类似的,因为两者都用了SQLAlchemy ORM来提供持久性存储。Pyramid中可以直接返回模板的context dictionary,而不用调用render函数。@view_config装饰器自动将返回的context传递给模板进行渲染。

[python]  view plain  copy
  1. <pre name="code" class="python">@view_config(route_name='newlunch', renderer='templates/index.pt', request_method='POST')   
  2.   
  3. def newlunch(request):   
  4.     l = Lunch(   
  5.         submitter=request.POST.get('submitter''nobody'),   
  6.         food=request.POST.get('food''nothing'),   
  7.         )   
  8.       
  9.     with transaction.manager:   
  10.         DBSession.add(l)   
  11.       
  12.     raise exc.HTTPSeeOther('/')  

 
 

表单数据是非常容易可以得到的,因为Pyramid自动将表单数据解析成一个字典供我们调用。ZopeTransactions模块保证了在多用户并发存储的情况下,不同的线程之间不会互相干扰。这也是一个值得一提的地方。

 

8                         总结

 Pyramid是三个框架中最灵活的。可以用作类似我们举的例子这种小应用,也可以大到Dropbox这类大型网站。Fedora开源社区选择Pyramid作为他们社区badges system的开发框架。最多的关于Pyramid的抱怨就是使用它的选择太多了,以至于想要新写一个项目必须要先确定好如何去实现。

目前为止最受欢迎的框架是Django,其大型应用例如:Bitbucket、Pinterest、Instagram和Onion。对于大多数需求,Django默认的解决方案都是非常明智的,因此其也非常适用于中到大型的web应用开发。

Flask对于那些希望用Python进行web开发同时开发的项目规模很小功能很简单的开发者而言,是不二的选择。其提供了很多简单的接口和工具供开发者使用,且开发出来的代码量非常小,配置文件的大小也非常小。

这篇文章中提到的区别之处,并不仅仅是表面上看到的那样可能只是些语法上的区别,更多地会影响到整个产品的设计和项目交付的速度。对于我们的示例程序,功能简单,因此Flask就能胜任。Django就会感到笨重。Pyramid的灵活性也没有体现出来。但是在实际的工作中,需求往往是一个接一个来且不断变化的,这时候希望你能想起来到底哪个才是最适合你的!

 

8.1                     鸣谢

感谢很多对这篇文章做出贡献的人!!(具体人名请参照原文!^_^)

猜你喜欢

转载自blog.csdn.net/qq_769932247/article/details/80499751