轉自:http://mp.weixin.qq.com/s?__biz=MzA4MjEyNTA5Mw==&mid=207397419&idx=1&sn=1629f9a29fde851d0287d135c22b6221#rd
1.簡介
在python web框架的世界裏充滿了選擇。有Django,Flask,Pyramid,Tornado,Bottle,Diesel,Pecan,Falcon等等的來吸引開發者的注意。作爲一個開發者,你想要從中選擇一個框架來幫你完成項目,並且能繼續做大事情。我們將關注Flask、Pyramid和Django。它們是微框架和商業級web服務的典範。
爲了讓你在這三個中做選擇的時候能夠更容易,我們會用每個框架來構建相同的應用,然後比較各自的代碼,突出它們的優勢和弱點。如果你想要代碼,可以直接跳到 框架實戰 章節或者在Github上下載代碼。
Flask是一個微框架,主要面向需求簡單的小應用。Pyramid和Django都是面向大的應用,但是在擴展性和靈活性上走了不同的路。Pyramid關注靈活性,讓開發者選擇合適的工具來開發項目。這意味着開發者可以選擇數據庫,URL結構,模板風格等等。Django的目標是提供web應用開發的一站式解決方案,所以相應的模塊也就比較多。
Django包含了一個ORM模塊,而Pyramid和Flask是讓開發者來選擇如何存儲數據。針對非Django框架的最流行的ORM目前是SQLAlchemy,也有很多其他的選擇,比如DynamoDB和MongoDB,亦或是像LevelDB和SQLite這樣的簡單本地持久化。Pyramid被設計可以使用任何持久層,甚至是還沒做好的。
2 關於框架
Django的一站式解決的思路能讓開發者不用在開發之前就在選擇應用的基礎設施上花費大量時間。Django有模板,表單,路由,認證,基本的數據庫管理等等內建功能。與之相反,Pyramid包含路由和驗證,但是模板和數據庫管理需要第三方庫。
用Flask和Pyramid來構建應用之前,選擇組件的時候會給開發者帶來更多的靈活性 ,可能有的應用場景不適合使用一個標準的ORM,或者需要與不同的工作流和模板系統交互。
Flask,這三個框架李最年輕的一個,創始與2010年年中。Pyramid框架來源於Pylons項目,在2010年末更名爲Pyramid,它最早發佈與2005年。Django發佈於2006年,就在Pylons項目之後。Pyramid和Django是非常成熟的框架,積累了大量的插件和擴展來滿足不同需要。
儘管Flask的歷史較短,但它能夠從以前的框架學到一些東西並且將它的目標設定在了小型項目上。它在一些僅有一兩個功能的小型項目上得到了大量應用。比如httpbin這樣的項目,簡單但非常強大,是一個幫助debug和測試HTTP的庫。
3 社區
Django的社區是最活躍的,在StackOverflow上有80000個相關問題和大量的博客和強大的用戶。Flask和Pyramid的社區就沒有這麼大了,但是它們的社區在郵件列表和IRC裏還是挺活躍的。在StackOverflow上只有5000個相關問題,Flask比Django的關注度小15倍。在Github上,它們的stars數相近,Django有11300個,Flask有10900個。這三個框架都是處於BSD衍生的開源許可證書之下。Flask和Django的證書都是3條款BSD,而Pyramid的是RPL,是4條款BSD證書的衍生版。
4 入門引導
Django和Pyramid都有內建的引導工具。Flask沒有,因爲Flask的主要受衆不是要構建大型MVC應用的。
4.1 Flask
Flask的Hello World應用的代碼是最簡單的,只用在一個Python文件裏碼7行代碼就夠了。
# 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沒有引導工具:因爲它根本不需要。從上面的Hello World應用的特點來看,一個沒什麼Python web開發經驗的人就可以很快的上手開始擼代碼。
對於需要把組件分離開的項目,Flask有blueprints。例如,你可以這樣構建你的應用,將與用戶有關的功能放在user.py裏,把與銷售相關的功能放在ecommerce.py裏,然後在site.py裏引用並添加到你的應用裏。我們暫時不會體驗這個功能了,它超出了我們的實例應用的需要。
4.2 Pyramid
Pyramid的引導工具叫pcreate,它是Pyramid的一部分。之前有一個Paste的工具,不過後來被Pyramid指定的工具鏈替代了。
$ pcreate -s starter hello_pyramid # Just make a Pyramid project
Pyramid希望能夠做比Flask更大和複雜的應用。因此,它的引導工具會創建一個更大的項目框架。它裏面包含一個配置文件,一個例子模板,還有文件能打包你的應用並上傳到Python Package Index(PYPI)。
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的引導工具特別的靈活。它沒有被限制在一個默認應用裏;pcreate可以使用任何數量的項目模板。包括我們在上面使用starter模板創建出來的,包含SQLAlchemy和ZODB支撐的項目。在PyPi上,可以找到依賴於Google App Engine,jQuery Mobile,Jinja2 templating,modern frontend frameworks等等的模板。
4.3 Django
Django也有自己的引導工具,它是django-admin的一部分。
django-admin startproject hello_django
django-admin startapp howdy # make an application within our project
我們已經可以看到Django和Pyramid的一些區別。Django把一個項目分成各自獨立的應用,而Pyramid和Flask認爲一個項目應該是一個包含一些視圖和模型的單個應用。也可以在Flask和Pyramid裏複製出像Django那樣的項目結構,但那不是默認的。
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只包含空的模型和模板文件,一個新的用戶可以看一點例子代碼就開始工作。它也能讓開發者選擇如何分配Django的應用。
這個引導工具的弱點是沒有引導用戶去打包他們的應用,新手往往會忽視這一點。如果一個開發者從未打包過應用,他們會發現在第一次部署的時候會有點亂糟糟的。一些有較大社區的項目,如django-oscar是被打包好的,並且能在PyPi上下載,但是在Github上,一些小的項目往往缺乏統一的打包。
5 模板
有一個能回覆HTTP請求的Python應用是個很好的開始,但是你的用戶不會用curl來與你的應用交互。幸運的是,這三個框架都提供了簡單的方法能讓你向HTML裏填充自定義信息,可以讓大家領略到你酷炫的前端技術。
模板能讓你直接向頁面中嵌入動態信息,而不用發送AJAX請求。這對於用戶體驗是有利的,你能一次加載整個頁面和它的動態數據。這對於移動端網站是非常重要的,能夠節省數秒鐘時間。
所有的我們使用的模板都依賴上下文提供動態信息,並將其通過模板渲染進HTML。模板的最簡單使用場景是很流行的一個例子,給已登錄的用戶的名字打招呼。也可以使用AJAX來獲取動態信息,但需要一個調用來獲取用戶名信息,這樣可能有點費事了,畢竟用模板來實現這麼簡單。
5.1 Django
我們這裏的使用場景非常簡單,假定我們有一個user對象,它有一個fullname的屬性,裏面包含一個用戶的名字。在Python裏,我們會這樣把當前用戶信息傳遞給模板:
def a_view(request):
# get the logged in user
# ... do more things
return render_to_response(
"view.html",
{"user": cur_user}
)
構成模板上下文非常簡單,只需要將一個有Python對象和數據結構的字典傳進模板即可。現在,我們需要通過名稱將其渲染進頁面,以免我們忘了它們是什麼。
<!-- 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 use 的聲明是用來針對如果沒有user的情況。匿名用戶不應該在頁面上方看到“you are logged in as”的字樣。
在if塊裏,你可以看到包括名字是被簡單的封裝在了{{}}裏。{{是用來放實際中嵌入模板的值,比如{{ user.fullname }}。
模板的另一個常見用法是展示一組東西,比如一個商業網站的存貨頁面。
def browse_shop(request):
# get items
return render_to_response(
"browse.html",
{"inventory": all_items}
)
模板裏,我們可以使用相同的 {% 來循環出存貨數據裏的所有項目,並且將其填充到URL和各自頁面。
{% for widget in inventory %}
<li><a href="/widget/{{ widget.slug }}/">{{ widget.displayname }}</a></li>
{% endfor %}
對於大多數普通的模板任務來說,Django能夠輕鬆實現,非常容易上手。
5.2 Flask
Flask默認使用一個受Django啓發而發展起來的名爲Jinja2的模板,但也可以通過配置來使用其他的語言。一個碼農可能會將Django和Jinja模板弄混。事實上,所有上面的Django模板的例子在Jinja2裏也是好使的。我們就不重複上面的例子了,我們來看看Jinja2比Django模板的一些更有表現力的特點。
Jinja和Django模板都提供一個叫過濾的特性,一個列表可以在被展示前傳給一個函數。一個博客如果包含分類的特性,就可以使用過濾來將一個分類下的文章篩選出來。
<!-- Django -->
<div class="categories">Categories: {{ post.categories|join:", " }}</div>
<!-- now in Jinja -->
<div class="categories">Categories: {{ post.categories|join(", ") }}</div>
在Jinja的模板語言裏,可以把任何數量的參數傳給過濾器,因爲Jinja像調用一個Python函數的方式來看待它,用圓括號來封裝參數。Django使用冒號來分隔過濾器名和參數,這樣就只能傳遞一個參數了。
Jinja和Django的 for 循環很相似。我們來看看它們的區別。在Jinja2, for-else-endfor 結構讓你能對一個列表進行迭代,也能處理列表爲空的情況。
{% 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版本的功能是一樣的,只是使用了 for-empty-endfor 這樣的結構替換了 for-else-endfor 的結構。
{% 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提供更多的執行環境的控制和高級特性。例如,它可以關閉潛在的危險特性來運行不受信任的模板,或者提前編譯模板以確保它們有效。
5.3 Pyramid
跟Flask類似,Pyramid支持很多模板語言(包括Jinja2和Mako),但它有一個默認的模板。Pyramid使用Chameleon,一個ZPT(the Zope Page Template)語言的實現。我們來看一個例子,把用戶的名字添加到網站頂端。相應的Python代碼有點類似但更加明確,不用調用render_template函數。
@view_config(renderer='templates/home.pt')
def my_view(request):
# do stuff...
return {'user': user}
但是我們的模板看起來就差別挺大了。ZPT是一個基於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)提供基本的條件語句,基本的字符串格式化,根據標記過濾內容。上面的例子只使用了TAL來完成相應工作。對於更加高級的工作,就需要TALES和METAL了。TALES(Template Attribute Language Expression Syntax)提供一些高級的字符串格式化,評估Python表達式,和引入表達式和模板。
METAL(Macro Expansion Template Attribute Language)是Chameleon模板裏最強大(和最複雜)的部分。Macros是可擴展的,可以被定義得像槽一樣,在macro被調用的時候填充上。
6. 框架實戰
對每個框架,我們來看看做一個叫wut4lunch的應用,這是一個社交網絡,可以告訴整個英特網這你晚飯吃了什麼。就用這個想法啓動,是個遊戲規則改變者。這個應用會是一個簡單的接口,能讓用戶PO出他們午飯吃了什麼,還可以看到別人吃了什麼。首頁看起來會是這個樣子:
6.1 用Flask做的應用
這個最短的實現,僅需34行Python代碼和一個22行的Jinja模板。首先,我們有一些例行公事的任務,比如初始化我們的應用和引入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)
首先,我們來看我們的模型,跟其他兩個例子裏的會差不多。
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))
Wow,就是這麼簡單。最難的地方是找到合適的SQLAlchemy 數據類型併爲數據庫的裏的字段選擇一個合適的長度。使用我們的模型是相當簡單的,要感謝SQLAlchemy 的查詢語法,我們在接下來會看到。
建立我們的提交表單灰常簡單。引入Flask-WTForms 並且弄好字段類型,你可以看到表單看起來就像我們的模型。主要的區別是新的提交按鈕和關於食品的提示和提交者名字的字段。
SECRET_KEY 字段在應用的配置裏是被WTForms使用的,用來創建CSRF token。它也被 isdangerous 使用,用來創建cookie和其他數據。
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() 查詢出來的一些已經PO出來的午餐,實例化一個表單,讓用戶PO出他們自己的美食冒險。爲了簡潔,變量用相同名稱傳遞給模板,但這不是必須的。
<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>
這是真正的模板了,我們循環出所有展示和喫掉的午餐,並將它們展示在<ul>標籤裏。這與之前看到的例子差不多。
<ul>
{% for lunch in lunches %}
<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) }}
{{ form.food.label }} {{ form.food(size=50) }}
{{ form.submit }}
</form>
</html>
模板裏<form> 的部分會渲染表單,將WTForm對象的輸入在root() 視圖傳入模板。當表單被提交的時候,它會發送一個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()
最後,我們只做了一點工作就完成了應用。我們用SQLAlchemy創建數據表來存儲午餐信息,然後運行我們寫的路由處理器。
6.2 Django的示例應用
Django版本的wut4lunch跟Flask版本的比較類似,但它分成了Django項目裏的幾個文件。首先,看最相近的部分:數據模型。唯一的區別就是SQLAlchemy 在聲明一個數據庫時的細微差別。
# 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裏使用的WTForm模塊,只是語法稍有不同。
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裏是一個捷徑,用來接受請求,模板路徑和上下文的字典。與Flask裏的 render_template 類似,但它也能接受進來的請求。
def newlunch(request):
l = Lunch()
l.submitter = request.POST['submitter']
l.food = request.POST['food']
l.save()
return redirect('home')
保存表單,應答數據庫是不同的,沒有用一個全局的數據庫session,Django讓我們調用模型的 save() 方法 並且能清楚的處理session,很巧妙!
Django提供了一些很棒的特性,能讓我們管理用戶提交的午餐,因而我們可以刪除掉那些看起來不適合我們網站的午餐。Flask和Pyramid沒有自動提供這個功能,在Django裏不用爲你的應用另寫一個管理頁面。畢竟碼農的時間也是時間!你需要做的,僅僅是在wut4lunch/admin.py 文件里加兩行代碼就行了。
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標籤使得調整你應用裏的URL而不用破壞視圖提供了可能。這能夠好使是因爲url標籤能夠向上尋找URL所指向的視圖。
<form action="{% url 'newlunch' %}" method="post">
{% csrf_token %}
{{ form.as_ul }}
<input type="submit" value="I ate this!" />
</form>
表單被用不同語法渲染, 我們需要在表單裏包含一個CSRF token ,這些不同主要是修飾性的。
6.3 Pyramid 的例子應用
終於,我們來看一下用Pyramid寫的例子應用。與Django和Flask最大的區別就是模板。稍微改動Jinja2模板就能解決Django的問題。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>
就像Django的模板,缺少 for-else-endfor 的結構讓邏輯看起來有點冗長。在這個例子裏,我們用 if-for 和 if-not-for 語句塊來提供相同的功能。模板使用XHTML標籤可能看起來與Django的不太一樣,AngularJS風格的模板使用 {{ 或 {% 來進行結構和條件控制。
Chameleon模板的一大優點是你選擇的編輯器可以正確的將語法高亮,因爲模板是基於XHTML的。對Django和Flask的模板來說,你的編輯器需要能夠支持相應的模板語言並且將其正確高亮。
<b>What are YOU eating?</b>
<form method="POST" action="/newlunch">
Name: ${form.text("submitter", size=40)}
What did you eat? ${form.text("food", size=40)}
<input type="submit" value="I ate this!" />
</form>
</html>
Pyramid裏渲染表單的方法可能有點冗長,因爲 pyramid_simpleform 沒有等同於Django的表單函數form.as_ul 的能夠自動渲染表單字段的功能。
現在,我們來看這個應用。首先,我們需要定義表單和生成首頁。
# 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裏,你可以直接返回你的模板上下文字典,而不用特別調用render函數。@view_config裝飾器會自動把你返回的內容渲染到模板。能夠不調用render函數,使得在Pyramid裏寫視圖能夠更加容易測試,因爲返回的數據沒有被湮沒在一個模板渲染對象裏。
@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裏能更容易的從請求對象裏獲取數據,它會自動把表單發送POST請求裏的數據解析,變成一個我們可以訪問的字典。爲了防止在同一時間,多個併發的請求訪問數據庫引發問題,ZopeTransaction模塊爲數據庫組提供了上下文管理器,將它寫進邏輯可以防止你的應用出現某個線程覆蓋其他線程數據的問題(線程安全),這在你的應用有很大流量並且使用一個全局session訪問數據庫的時候是個嚴重的問題。
7 總結
Pyramid在這三個框架裏是最靈活的。它可以用來寫小的應用,它也能來支持像Dropbox這樣大名鼎鼎的網站。像Fedora這樣的開源社區選擇它來做一些應用, 比如他們的社區badges_system,它會獲取關於很多項目工具的事件來給用戶頒發成就獎。對Pyramid最多的抱怨是它有如此多的選擇,在開始新項目的時候可能會有點糾結。
目前最流行的框架是Django,有一堆網站用它。有Bitbucket、Pinterest、Instagram、The Onion來完成網站功能的全部或一部分。對於一些有普遍需求的網站,選Django是非常理智的,因爲它對於中到大型的web應用是個非常流行的選擇。
Flask適合開發者用最快的速度做一個簡單的,Python做後端的網站。它適合一些一次性的工具,或者一些基於現有API的簡單web應用。需要一個簡單的web接口的後端項目可以開發的很快,一些需要少量配置的應用可以在Flask的前端上受益,比如jitviewer ,它就可以提供web接口來查看 PyPi的即時編譯日誌。
這三個框架都對一些需求提供瞭解決方案,我們可以來看看它們的區別。有些區別不僅僅是表面的,它會影響你如何設計產,多快能實現特性並且修復問題。因爲我們的例子都很小,我們可以看到在小規模項目的時候,Flask非常棒,而Django就有點笨重了。Pyramid的靈活性沒有成爲一個要素,因爲我們的需求是一樣的,但是現實delig中的需求都是非常隨機的。
7.1 結尾致謝
標題圖片裏的logo來自Flask Django Pyramid 項目的官網。
能完成這篇文章要感謝它的審稿人,Remy DeCausemaker、Ross Delinger 和 Liam Middlebrook,對初稿提出了大量意見。
這邊文章收到很多評論和指正,感謝 Adam Chainz、bendwarn、Sergei Maertens、Tom Leo、wichert。
英文出處:www.airpair.com
本文由 伯樂在線 - 賤聖OMG 翻譯,Daetalus 校稿。
譯文鏈接:http://python.jobbole.com/81396/