Django vs Flask vs Pyramid: 如何去选择一个Python Web框架

Djangovs Flask vs Pyramid: 如何去选择一个PythonWeb框架

 

原文链接:https://www.airpair.com/python/posts/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应用。

# 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没有内嵌引导程序的原因了:没有这样的需求。

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

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

5.2                     Pyramid

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

$ pcreate-s starter hello_pyramid # Just make a Pyramid project

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

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

5.3                     Django

 

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

django-admin startproject hello_django
django-admin startapp howdy # make an application within our project

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

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和模板文件,新用户只能看到一些简单的示例代码。

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

 

6                         模板

 

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

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

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

6.1                     Django

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

def a_view(request):
    # get the logged in user
    # ... do more things
    return render_to_response(
        "view.html",
        {"user": cur_user}
    )

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

<!-- view.html -->
<div class="top-bar row">
  <div class="col-md-10">
  <!-- more top bar things go here -->
  </div>
  {% if user %}
  <div class="col-md-2 whoami">
    You are logged in as {{ user.fullname }}
  </div>
  {% endif %}
</div>

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

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

 

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

def browse_shop(request):
    # get items
    return render_to_response(
        "browse.html",
        {"inventory": all_items}
    )

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

{% for widget in inventory %}
    <li><a href="/widget/{{ widget.slug }}/">{{ widget.displayname }}</a></li>
{% endfor %}

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

 

6.2                     Flask

 

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

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

<!-- Django -->
<div class="categories">Categories: {{ post.categories|join:", " }}</div>

<!-- now in Jinja -->
<div class="categories">Categories: {{ post.categories|join(", ") }}</div>

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

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

{% for item in inventory %}
<div class="display-item">{{ item.render() }}</div>
{% else %}
<div class="display-warn">
<h3>No items found</h3>
<p>Try another search, maybe?</p>
</div>
{% endfor %}

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

{% for item in inventory %}
<div class="display-item">{{ item.render }}</div>
{% empty %}
<div class="display-warn">
<h3>No items found</h3>
<p>Try another search, maybe?</p>
</div>
{% endfor %}

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

6.3                     Pyramid

 

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

@view_config(renderer='templates/home.pt')
def my_view(request):
    # do stuff...
    return {'user': user}

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

<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(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获取信息。

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)

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

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 data类型和长度。

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

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

from flask.ext.wtf import Form 
from wtforms.fields import StringField, SubmitField 
app.config['SECRET_KEY'] = 'please, tell nobody' 

class LunchForm(Form): 
    submitter = StringField(u'Hi, my name is') 
    food = StringField(u'and I ate') 
    # submit button will read "share my lunch!" 
    submit = SubmitField(u'share my lunch!')
    

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

from flask import render_template 

@app.route("/") 
def root(): 
    lunches = Lunch.query.all() 
    form = LunchForm() 
    return render_template('index.html', form=form, lunches=lunches)

 

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

首先是开头的介绍:

<html> 
<title>Wut 4 Lunch</title> 
<b>What are people eating?</b> 
<p>Wut4Lunch is the latest social network where you can tell all your friends about your noontime repast!</p>


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

 

<li><strong>{{ lunch.submitter|safe }}</strong> just ate <strong>{{ lunch.food|safe }}</strong>
{% else %} 
<li><em>Nobody has eaten lunch, you must all be starving!</em></li> 
{% endfor %} 
</ul> 
<b>What are YOU eating?</b> 
<form method="POST" action="/new"> 
    {{ form.hidden_tag() }} 
    {{ form.submitter.label }} {{ form.submitter(size=40) }} 
    <br/> 
    {{ form.food.label }} {{ form.food(size=50) }} 
    <br/> 
    {{ form.submit }} 
</form> 
</html>

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

from flask import url_for, redirect 
@app.route(u'/new', methods=[u'POST']) 
def newlunch(): 
    form = LunchForm() 
    if form.validate_on_submit(): 
        lunch = Lunch() 
        form.populate_obj(lunch) 
        db.session.add(lunch) 
        db.session.commit() 
    return redirect(url_for('root'))

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

if __name__ == "__main__": 
    db.create_all() 
    # make our sqlalchemy tables 
    app.run()
最终我们建立数据库并启动我们的app。 代码量非常的少!! 

7.2                     Django

 

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

# 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)


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

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的实例并将其传递给模板。

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中用来处理request、模板路径和内容的,类似于Flask中的render_template。

def newlunch(request): 
    l = Lunch() 
    l.submitter = request.POST['submitter'] 
    l.food = request.POST['food'] 
    l.save() 
    return redirect('home')

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


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

from wut4lunch.models import Lunch
admin.site.register(Lunch)

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

<ul> 
{% for lunch in lunches %} 
<li><strong>{{ lunch.submitter }}</strong> just ate <strong>{{ lunch.food }}</strong></li> 
{% empty %} 
<em>Nobody has eaten lunch, you must all be starving!</em> 
{% endfor %} 
</ul>

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

<form action="{% url 'newlunch' %}" method="post"> 
    {% csrf_token %} 
    {{ form.as_ul }} 
    <input type="submit" value="I ate this!" /> 
</form>

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

 

7.3                     Pyramid

 

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

<!-- 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>

表单是这样的:

<pre name="code" class="html"><b>What are YOU eating?</b> 
<form method="POST" action="/newlunch"> 
    Name: ${form.text("submitter", size=40)} 
    <br/> 
    What did you eat? ${form.text("food", size=40)} 
    <br/> 
    <input type="submit" value="I ate this!" /> 
</form> 
</html>

 

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

# pyramid_wut4lunch/views.py 

class LunchSchema(Schema): 
    submitter = validators.UnicodeString() 
    food = validators.UnicodeString() 
    
@view_config(route_name='home', renderer='templates/index.pt') 

def home(request): 
    lunches = DBSession.query(Lunch).all() 
    form = Form(request, schema=LunchSchema()) 
    return {'lunches': lunches, 'form': FormRenderer(form)}

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

<pre name="code" class="python">@view_config(route_name='newlunch', renderer='templates/index.pt', request_method='POST') 

def newlunch(request): 
    l = Lunch( 
        submitter=request.POST.get('submitter', 'nobody'), 
        food=request.POST.get('food', 'nothing'), 
        ) 
    
    with transaction.manager: 
        DBSession.add(l) 
    
    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                     鸣谢

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


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值