PythonWeb框架:DjangovsFlaskvsPyramid

Pyramid, Django, 和 Flask都是优秀的框架,为项目选择其中的哪一个都是伤脑筋的事。我们将会用三种框架实现相同功能的应用来更容易的对比三者。也可以直接跳到框架实战(Frameworks in Action)章节查看代码(code)。

让客户满意是我们工作的目标,不断超越客户的期望值来自于我们对这个行业的热爱。我们立志把好的技术通过有效、简单的方式提供给客户,将通过不懈努力成为客户在信息化领域值得信任、有价值的长期合作伙伴,公司提供的服务项目有:主机域名虚拟主机、营销软件、网站建设、三门峡网站维护、网站推广。

1. 简介

世界上可选的基于Python的web框架有很多。Django, Flask, Pyramid, Tornado, Bottle, Diesel, Pecan, Falcon等等,都在争取开发者支持。作为一开发者从一堆选择中筛选出一个来完成项目将会成为下一个大工程。我们今天专注于Flask, Pyramid, 和 Django。它们涵盖了从小微项目到企业级的web服务。

为了更容易在三者中作出选择(至少更了解它们),我们将用每一个框架构建同样的应用并比较它们的代码,对于每一个方法我们会高亮显示它的优点和缺点。如果你只想要代码,直接跳到框架实战章节(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.关于框架

Django的”batteries included” 特性让开发者不需要提前为他们的应用程序基础设施做决定,因为他们知道Python已经深入到了web应用当中。Django已经内建了模板、表单、路由、认证、基本数据库管理等等。比较起来,Pyramid包括路由和认证,但是模板和数据库管理需要额外的库。

前面为 Flask和Pyramid apps选择组件的额外工作给那些使用案例不适用标准ORM的开发者提供了更多的灵活性,同样也给使用不同工作流和模版化系统的开发者们带来了灵活性。

Flask,作为三个框架里面最稚气的一个,开始于2010年年中。Pyramid框架是从Pylons项目开始的,在2010年底获得 Pyramid这个名字,虽然在2005年就已经发布了***个版本。Django 2006年发布了***个版本,就在Pylons项目(***叫Pyramid)开始之后。Pyramid和Django都是非常成熟的框架,积累了众多插件和扩展以满足难以置信的巨大需求。

虽然Flask历史相对更短,但它能够学习之前出现的框架并且把注意力放在了微小项目上。它大多数情况被使用在一些只有一两个功能的小型项目上。例如 httpbin,一个简单的(但很强大的)调试和测试HTTP库的项目。

3. 社区

***活力的社区当属Django,其有80,000个StackOverflow问题和一系列来自开发者和优秀用户的良好的博客。Flask和Pyramid社区并没有那么大,但它们的社区在邮件列表和IRC上相当活跃。StackOverflow上仅有5,000个相关的标签,Flask比Django小了15倍。在Github上,它们的star近乎相当,Django有11,300个,Flask有10,900个。

三个框架都使用的是BSD衍生的协议。Flask和Django的协议是BSD 3条款,Pyramid的Repoze Public License RPL是BSD协议 4条款的衍生。

4. Bootstrapping

Django和Pyramid都内建bootstrapping工具。Flask没有包含类似的工具,因为Flask的目标用户不是那种试图构建大型MVC应用的人。

4.1 Flask

Flask的hello world应用非常的简单,仅仅单个Python文件的7行代码就够了。

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

这是Flask没有bootstrapping工具的原因:没有它们的需求。从Flask主页上的Hello World特性看,没有构建Python web应用经验的开发者可以立即开始hacking。

对于各部分需要更多分离的项目,Flask有blueprints。例如,你可以将所有用户相关的函数放在users.py中,将销售相关的函数放在ecommerce.py中,然后在site.py中添加引用它们来结构化你的Flask应用。我们不会深入这个功能,因为它超出了我们展示demo应用的需求。

4.2 Pyramid

Pyramid 的 bootstrapping工具叫 pcreate,是Pyramid的组成部分. 之前的 Paste 工具套装提供了 bootstrapping ,但是从那之后被 Pyramid专用工具链替代了。

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

Pyramid 比 Flask 适用于更大更复杂的应用程序. 因为这一点,它的 bootstrapping工具创建更大的项目骨架. Pyramid 同样加入了基本的配置文件,一个例子模版和用于将程序打包上传到 Python Package Index的所有文件。

 
 
 
 
  1. hello_pyramid 
  2.  
  3. ├── CHANGES.txt 
  4.  
  5. ├── development.ini 
  6.  
  7. ├── MANIFEST.in 
  8.  
  9. ├── production.ini 
  10.  
  11. ├── hello_pyramid 
  12.  
  13. │   ├── __init__.py 
  14.  
  15. │   ├── static 
  16.  
  17. │   │   ├── pyramid-16x16.png 
  18.  
  19. │   │   ├── pyramid.png 
  20.  
  21. │   │   ├── theme.css 
  22.  
  23. │   │   └── theme.min.css 
  24.  
  25. │   ├── templates 
  26.  
  27. │   │   └── mytemplate.pt 
  28.  
  29. │   ├── tests.py 
  30.  
  31. │   └── views.py 
  32.  
  33. ├── README.txt 
  34.  
  35. └── 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

Django 也有自己的 bootstrap 工具, 内置在 django-admin 中.

 
 
 
 
  1. django-admin startproject hello_django 
  2.  
  3. django-admin startapp howdy # make an application within our project  

Django 跟 Pyramid 区别在于: Django 由多个应用程序组成一个项目, 而 Pyramid 以及 Flask 项目是包含 View 和 Model 单一应用程序 . 理论上, Flask 和 Pyramid 的项目允许存在多个 project/app, 不过在默认配置中只能有一个.

 
 
 
 
  1. hello_django 
  2.  
  3. ├── hello_django 
  4.  
  5. │   ├── __init__.py 
  6.  
  7. │   ├── settings.py 
  8.  
  9. │   ├── urls.py 
  10.  
  11. │   └── wsgi.py 
  12.  
  13. ├── howdy 
  14.  
  15. │   ├── admin.py 
  16.  
  17. │   ├── __init__.py 
  18.  
  19. │   ├── migrations 
  20.  
  21. │   │   └── __init__.py 
  22.  
  23. │   ├── models.py 
  24.  
  25. │   ├── tests.py 
  26.  
  27. │   └── views.py 
  28.  
  29. └── manage.py  

Django 默认只在项目中创建 空白的 model 和模板文件, 供新手参考的示范代码不多. 此外, 开发者在发布应用程序的时候, 还要自己配置, 这也是个麻烦.

bootstrap 工具的缺点是没有指导开发者如何打包应用. 对于那些没有经验的新手来说, ***次部署应用将是个很头疼的问题. 像 django-oscar 这样的大社区, 项目都是打包好了, 放在 PyPi 上供大家安装. 但是 Github 上面的小项目缺少统一的打包方式.

5. 模板

一个Python应用能够响应HTTP请求将是一个伟大的开端,但是有可能你的大多数用户是没有兴趣使用curl与你的web应用交互的。幸运的是,这三个竞争者提供了使用自定义信息填充HTML的方法,以便让大伙们能够享受时髦的Bootstrap 前端。

模板让你能够直接向页面注入动态信息,而不是采用AJAX。你只需要一次请求就可以获取整个页面以及所有的动态数据,这对用户体验来说是很好的。这对于手机网站来说尤其重要,因为一次请求花费的时间会更长。

所有的模板选项依赖于“上下文环境(context)”,其为模板转换为HTML提供了动态信息。模板的最简单的例子是填充已登录用户的名字以正确的迎接他们。也可以用AJAX获取这种动态信息,但是用一整个调用来填写用户的名字有点过头了,而同时模板又是这么的简单。

5.1 Django

我们使用的例子正如写的那么简单,假设我们有一个包含了用户名的funllname属性的user对象。在Python中我们这样向模板中传递当前用户:

 
 
 
 
  1. def a_view(request): 
  2.  
  3.     # get the logged in user 
  4.  
  5.     # ... do more things 
  6.  
  7.     return render_to_response( 
  8.  
  9.         "view.html", 
  10.  
  11.         {"user": cur_user} 
  12.  
  13.     )  

拥有这个模板的上下文很简单,传入一个Python对象的字典和模板使用的数据结构。现在我们需要在页面上渲染他们的名字,以防页面忘了他们是谁。

 
 
 
 
  1.  
  2.  
  3.  
  4.  
  5.    
  6.  
  7.    
  8.  
  9.   
 
  •  
  •   {% if user %} 
  •  
  •    
  •  
  •     You are logged in as {{ user.fullname }} 
  •  
  •   
  •  
  •  
  •   {% endif %} 
  •  
  •   

    首先,你会注意到这个 {% if user %} 概念。在Django模板中, {% 用来控制循环和条件的声明。这里的if user声明是为了防止那些不是用户的情况。匿名用户不应该在页面头部看到“你已经登录”的字样。

    在if块内,你可以看到,包含名字非常的简单,只要用{{}}包含着我们要插入的属性就可以了。{{是用来向模板插入真实值的,如{{ user.fullname }}。

    模板的另一个常用情况是展示一组物品,如一个电子商务网站的存货清单页面。

     
     
     
     
    1. def browse_shop(request): 
    2.  
    3.     # get items 
    4.  
    5.     return render_to_response( 
    6.  
    7.         "browse.html", 
    8.  
    9.         {"inventory": all_items} 
    10.  
    11.     )  

    在模板中,我们使用同样的{%来循环清单中的所有条目,并填入它们各自的页面地址。

     
     
     
     
    1. {% for widget in inventory %} 
    2.  
    3. {{ widget.displayname }}
    4.  
    5.  
    6. {% endfor %}  

    为了做大部分常见的模板任务,Django可以仅仅使用很少的结构来完成目标,因此很容易上手。

    5.2 Flask

    Flask默认使用受Django启发的Jinja2模板语言,但也可以配置来使用另一门语言。不应该抱怨一个仓促的程序员分不清Django和Jinja模板。事实是,上面的Django例子在Jinja2也有效。为了不去重复相同的例子,我们来看下Jinja2比Django模板更具表现力的地方。

    Jinja和Django模板都提够了过滤的特性,即传入的列表会在展示前通过一个函数。一个拥有博文类别属性的博客,可以利用过滤特性,在一个用逗号分割的列表中展示博文的类别。

     
     
     
     
    1.  
    2.  
    3. Categories: {{ post.categories|join:", " }}   
    4.  
    5.  
    6.  
    7. Categories: {{ post.categories|join(", ") }}  

    在Jinja模板语言中,可以向过滤器传入任意数量的参数,因为Jinja把它看成是 使用括号包含参数的Python函数的一个调用。Django使用冒号来分割过滤器的名字和过滤参数,这限制了参数的数目只能为一。

    Jinjia和Django的for循环有点类似。我们来看看他们的不同。在Jinjia2中,for-else-endfor结构能遍历一个列表,同时也处理了没有项的情况。

     
     
     
     
    1. {% for item in inventory %} 
    2.  
    3. {{ item.render() }} 
    4.  
    5. {% else %} 
    6.  
    7.  
    8.  
    9. No items found

       
    10.  
    11. Try another search, maybe?

       
    12.  
    13.  
    14.  
    15. {% endfor %}  

    Django版的这个功能是一样的,但是是用for-empty-endfor而不是for-else-endfor。

     
     
     
     
    1. {% for item in inventory %} 
    2.  
    3. {{ item.render }} 
    4.  
    5. {% empty %} 
    6.  
    7.  
    8.  
    9. No items found

       
    10.  
    11. Try another search, maybe?

       
    12.  
    13.  
    14.  
    15. {% endfor %}  

    除了语法上的不同,Jinja2通过执行环境和高级特性提供了更多的控制。例如,它可以关闭危险的特性以安全的执行不受信任的模板,或者提前编译模板以确保它们的合法性。

    5.3 Pyramid

    与Flask类似,Pyramid支持多种模板语言(包括Jinja2和Mako),但是默认只附带一个。Pyramid使用Chameleon,一个 ZPT (Zope Page Template) 模板语言的实现。我们来回头看看***个例子,添加用户的名字到网站的顶栏。Python代码除了明确调用了render_template函数外其他看起来都差不多。

     
     
     
     
    1. @view_config(renderer='templates/home.pt') 
    2.  
    3. def my_view(request): 
    4.  
    5.     # do stuff... 
    6.  
    7.     return {'user': user}  

    但是我们的模板看起来有些不同。ZPT是一个基于XML得模板标准,所以我们使用了类XSLT语句来操作数据。

     
     
     
     
    1.  
    2.  
    3.    
    4.  
    5.    
    6.  
    7.    
    8.  
    9.   
    10.  
    11.        tal:content="string:You are logged in as ${user.fullname}" 
    12.  
    13.        class="col-md-2 whoami"> 
    14.  
    15.    
    16.  
    17.   

    Chameleon对于模板操作有三种不同的命名空间。TAL(模板属性语言)提供了基本的条件语句,字符串的格式化,以及填充标签内容。上面的例子只用了TAL来完成相关工作。对于更多高级任务,就需要TALES和METAL。TALES( 模板属性表达式语法的语言)提供了像高级字符串格式化,Python表达式评估,以及导入表达式和模板的表达式。

    METAL(宏扩展模板属性语言)是Chameleon模板***大的(和复杂的)一部分。宏是可扩展的,并能被定义为带有槽且当宏被调用时可以被填充。

    6. 利用框架行动起来

    对于各个框架,我们将通过制作一个叫做wut4lunch的应用来了解,这个应用是告诉整个互联网你午饭吃了什么的社交网络。很自由的一个起始想法,完全可以随意改变。应用将有一个简单的接口,允许用户提交他们午饭的内容,并看到其他用户吃的什么的列表。主页完成后将看起来像这样。

    6.1 使用Flask的Demo应用

    最短的实现用了34行Python代码和一个22行的Jinja模板。首先,我们有些管理类的任务要做,比如初始化我们的应用并拉近我们的ORM。

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

    现在我们看下我们的模型,这将和另两个样例基本一样。

     
     
     
     
    1. class Lunch(db.Model): 
    2.  
    3.     """A single lunch""" 
    4.  
    5.     id = db.Column(db.Integer, primary_key=True) 
    6.  
    7.     submitter = db.Column(db.String(63)) 
    8.  
    9.     food = db.Column(db.String(255))  

    哇,相当简单。最难的部分是找到合适的 SQLAlchemy数据类型,选择数据库中String域的长度。使用我们的模型也超级简单,这在于我们将要看到 SQLAlchemy查询语法。

    构建我们的提交表单也很简单。在引入Flask-WTForms和正确的域类型后,你可以看到表单看起来有点像我们的模型。主要的区别在于新的提交按钮和食物与提交者姓名域的提示。

    应用中的SECRET_KEY域是被WTForms用来创建CSRF符号的。它也被itsdangerous(Flask内包含)用来设置cookies和其他数据。

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

    让表单在浏览器中显示意味着模板要有它。我们像下面那样传递进去。

     
     
     
     
    1. from flask import render_template   
    2.  
    3. @app.route("/") 
    4.  
    5. def root(): 
    6.  
    7.     lunches = Lunch.query.all() 
    8.  
    9.     form = LunchForm() 
    10.  
    11.     return render_template('index.html', form=form, lunches=lunches)  

    好了,发生了什么?我们得到已经用Lunch.query.all()提交的午餐列表,并实例化一个表单,让用户提交他们自己的美食之旅。为了简化,变量使用相同的名字出入模板,但这不是必须的。

     
     
     
     
    1.  
    2.  
    3. Wut 4 Lunch 
    4.  
    5. What are people eating? 
    6.  
    7. Wut4Lunch is the latest social network where you can tell all your friends 

    8.  
    9. about your noontime repast!

        

    这就是模板的真实情况,我们在已经吃过的午餐中循环,并在