Djangovs Flask vs Pyramid: 如何去选择一个PythonWeb框架
原文链接:https://www.airpair.com/python/posts/django-flask-pyramid
Pyramid,Django和Flask都是非常不错的Web框架,如何为你的项目从中选择最合适的是一个问题。本文中,会使用这三个Web框架来实现具备同一个功能的网站,以此来进行对比。
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等。
Django自带所有所需模块的模式使得开发者能够非常容易地直接关注web应用开发本身,而不需要去做很多关于应用架构如何设计的决定。Django内嵌了模板、表单、路由、认证、基本数据库管理等模块。Pyramid包括路由和认证,但是模板和数据库管理则需要额外的库。
Flask,三个框架中最年轻的一个,始于2010年中。Pyramid框架出自Pylons,在2010年末得名,最早的一个版本是在2005年。Django在2006年发布第一个版本,就在Pylons刚开始的时候。Pyramid和Django都是非常成熟的框架,有相当多的插件和扩展模块来满足各类需求。
虽然Flask更年轻一些,但其更有机会去学习之前的框架并把自己的关注点放到了小项目上。其经常被只有一两个功能的小项目使用,比如httpbin,http://httpbin.org/,一个简单但是强力的调试和测试HTTP的库。
从StackOverflow上各个框架相关的问题数量就可以看出哪个框架更加受欢迎了。
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中。这里我们不再举例说明了,超出了这个实例程序讨论的范围。
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
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和模板文件,新用户只能看到一些简单的示例代码。
引导程序不引导用户去打包他们的应用,这种做法有一个不足之处,是新手没有这个意识去这样做。如果一个开发者之前没有打包过应用,那么他会发现他第一次部署的时候会有多么的困难。但基本上小型的项目都没有统一打包。
有了基本的HTTP 服务器还远远不够,用户肯定希望你还能有个风光靓丽的界面。
模板让你能够动态地更改页面信息,而不要调用AJAX。这个机制从用户的角度来说非常的友好,因为你只需要取一次全页面的信息和其他动态的数据,对于一些移动设备访问网站来说,这样的行为能为他们节省许多时间。
所有的模板都基于context,提供动态的信息给模板并最终渲染到HTML中。让我们来看一个例子,将用户的登录名显示给他们看。
我们的示例非常简单,假设我们有一个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属性。
首先,你会发现其中{% 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 %}
{{ widget.displayname }}
{% endfor %}
对于大部分的需求来说,Django的模板系统可以通过非常简单的结构来满足。
Flask默认使用Django推荐的Jinja2模板语言,但是也可以配置成使用其他的语言。实际上Jinja2和Django的模板系统还是有一些不同之处的,Jinja2更加易读。
Jinja2和Django的模板系统偶读提供了filtering功能。一个list在被显示之前可以被一个指定的函数进行处理。例如:
Categories: {{ post.categories|join:", " }}
Categories: {{ post.categories|join(", ") }}
Jinja2的模板语言中,能够传递任意数量的参数给filter,因为Jinja2进行的是函数的调用,将参数传递给filter之后的函数。Django使用一个冒号作为filter和其参数的分隔符,这导致了参数的数量被限制在了1个,因为只能有一个冒号,冒号后面的内容都被认作为是参数。
Jinja2和Django的模板系统对于for循环都是类似的,让我们来看看他们的区别在哪里。在Jinja2中,附带了for-else-endfor架构,让你能够遍历list,并且在没有内容的情况下进入else进行处理。
{% for item in inventory %}
{{ item.render() }}
{% else %}
No items found
Try another search, maybe?
{% endfor %}
Django也有类似的结构,但是关键字是empty。如:
{% for item in inventory %}
{{ item.render }}
{% empty %}
No items found
Try another search, maybe?
{% endfor %}
除了上面提到的各类区别,Jinja2提供了更多的控制或功能。比如其安全性和提前编译模板以避免语法错误等功能,使其面对Django更优一筹。
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类似的语法:
Chameleon实际上使用三个不同的命名空间来进行模板内的操作。TAL(template attribute language)提供了基本的条件判断、字符串格式化和填充tag内容等操作。上面的例子就是使用了tal来进行填充。对于更高级的用法,我们就需要使用到TALES和METAL。TALES(Template Attribute Language Expression Syntax)提供了高级字符串处理的表达式和Python表达式求值、表达式导入和模板等。
METAL(Macro Expansion Template Attribute Language)是最强大也是最复杂的部分。其是可扩展的。
下面我们来创建一个APP叫做wut4lunch,用来告诉别人你午饭吃了什么。这个APP允许用户上传他们午餐吃了什么,然后浏览别人吃的什么,预期主页面是这个样子的:
代码非常简单,主要包括初始化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()获取到所有已经上传的午餐信息,并且初始化了一个表单供用户上传他们自己的数据。接下来是模板的内容:
首先是开头的介绍:
Wut 4 Lunch
What are people eating?
Wut4Lunch is the latest social network where you can tell all your friends about your noontime repast!
接着是最重要的部分,将我们查询的结果通过循环遍历出来,全部呈现到页面上:
{{ lunch.submitter|safe }} just ate {{ lunch.food|safe }}
{% else %}
Nobody has eaten lunch, you must all be starving!
{% endfor %}
What are YOU eating?