python中常用的幾個web框架有django, tornado, flask等,今天來總結一下django和tornado的不同。工作中django和tornado都用過,使用django相對更多一些。個人感覺django雖然好用,有搭建項目快、自帶ORM、自動生成路由、自帶管理後臺等優勢;但若實際工作中選擇,我還是會偏向於使用tornado框架,因爲torndo使用更加靈活,並且支持websocket,tcp等通信協議,最重要的是tornado是異步非阻塞的web框架;而在django中要實現websocket、異步非阻塞等功能則需要引入dwebsocket、celery等第三方模塊。
本文使用的環境是python3.6, django2.0, tornado5.1。
下面主要從以下幾個方面介紹一下這兩個框架的不同:
1.創建項目的方式
2.數據庫連接
3.異步非阻塞請求
4.websocket的使用
1.項目創建方式
1)django
django主要是通過下面兩個命令創建項目:
django-admin startproject Test # 創建項目,名稱爲Test
django-admin startpapp Test01 # 創建app,名稱爲Test01
執行完成後,會生成如下的目錄結構:
D:.
│ manage.py
│ test.txt
│
├─.idea
│ │ misc.xml
│ │ modules.xml
│ │ Test.iml
│ │ workspace.xml
│ │
│ └─inspectionProfiles
│ profiles_settings.xml
│
├─Test
│ settings.py
│ urls.py
│ wsgi.py
│ __init__.py
│
└─Test01
│ admin.py
│ apps.py
│ models.py
│ tests.py
│ views.py
│ __init__.py
│
└─migrations
__init__.py
主要是manage.py,Test,Test01這幾個文件和文件夾,
manage.py是管理項目的文件,通過它運行django的一些內置命令,如模型遷移、啓動項目等;
Test/settings.py是配置文件,項目配置存放在這裏
Test/urls.py是路由文件,負責分發http請求
Test01/models.py是模型文件,Test01下創建的模型就放在這裏,模型負責將表結構映射到數據庫中
Test01/views.py是視圖文件,django中的視圖在這裏定義
Test01/migrations目錄中存放遷移後生成的遷移文件。
django項目的基本結構就是這樣。
2)tornado
tornado項目的創建比較靈活,沒有什麼項目名稱和app的概念,全靠自己組織項目,就是創建一個個python文件和python package。可以像下面一樣來組織tornado項目:
├── App
│ ├── __init__.py
│ ├── Shop
│ │ ├── __init__.py
│ │ └── views.py
│ └── User
│ ├── __init__.py
│ └── views.py
├── application.py
├── Config
│ ├── config_base.py
│ ├── config_db.conf
│ ├── config_db_get.py
│ ├── config_engine.py
│ ├── __init__.py
├── Models
│ ├── __init__.py
│ ├── Shop
│ │ └── __init__.py
│ └── User
│ ├── BaseClass.py
│ ├── __init__.py
│ └── UserModel.py
├── server.py
├── static
│ └── __init__.py
├── templates
│ └── __init__.py
├── test.py
└── Urls
├── __init__.py
├── Shop.py
└── User.py
這裏有幾個主要文件App, Config, Models, Urls, static, templates, application.py, server.py。
項目的app可以集中放在App目錄中,與數據庫對應的模型文件可以放在Models中,http路由可以放在Urls中,項目配置信息可以放在Config目錄中,靜態文件和模板分別放在static和templates中。application.py文件可以加載路由信息和項目配置信息,server.py文件負責啓動項目。
項目的基本配置信息可以放在Config/config_base.py中,如下:
# coding=utf-8
import os
BASE_DIR = os.path.dirname(__file__)
# 參數
options = {
"port": 8001,
}
# 基本配置信息
settings = {
"debug": True,
"static_path": os.path.join(BASE_DIR, "static"),
"template_path": os.path.join(BASE_DIR, "templates")
}
路由信息可以放在Urls/User.py中,如下:
# coding=utf-8
from App.UserInfo import views
user_urls = [
(r'/user/', views.IndexHandler),
]
application.py中加載路由信息和配置信息:
# coding=utf-8
from tornado import ioloop, httpserver
from application import Application
from Config import config_base
if __name__ == '__main__':
app = Application()
http_server = httpserver.HTTPServer(app)
http_server.listen(config_base.options.get("port"))
ioloop.IOLoop.current().start()
2.數據庫連接
1)django
django中使用數據庫時,首先要在settings.py中配置數據庫信息:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', # 數據庫引擎
'NAME': 'django_test', # 你要存儲數據的庫名,事先要創建之
'USER': 'root', # 數據庫用戶名
'PASSWORD': 'test', # 密碼
'HOST': 'localhost', # 主機
'PORT': '3306', # 數據庫使用的端口
}
}
然後在每個app下編寫完models.py後,執行以下兩個命令後,就可以使用數據庫了:
python manage.py makemigrations
python manage.py migrate
可以調用模型管理器對象objects的相應方法,執行增刪改查等操作。
2)tornado
這裏說一下在tornado中使用sqlalchemy連接數據庫,需要安裝sqlalchemy和pymysql。
2.2.1)首先在Config/config_db.conf中配置數據庫信息:
[db_user]
name = db_tornado03
port = 3306
user = root
host = 127.0.0.1
pass = test
pool_size = 3
2.2.2)然後在Config/config_engine.py中配置engine:
# coding=utf-8
from sqlalchemy import create_engine
from Config.config_db_get import ConfigDBUser
# 數據庫配置信息 可以配置多個engine, 每個數據庫對應一個engine
db_user = ConfigDBUser("db_user")
engine_user = create_engine(
"mysql+pymysql://%s:%s@%s:%d/%s" % (
db_user.get_db_user(),
db_user.get_db_pass(),
db_user.get_db_host(),
db_user.get_db_port(),
db_user.get_db_database()
),
encoding='utf-8',
echo=True,
pool_size=20,
pool_recycle=100,
connect_args={"charset": 'utf8mb4'}
)
create_engine用來初始化數據庫連接。
2.2.3)在Models/UserInfo/BaseClass.py中配置連接數據庫的session信息:
# coding=utf-8
from sqlalchemy.orm import scoped_session, sessionmaker
from Config.config_engine import engine_user
class BaseClass:
def __init__(self):
# 創建session對象,並且用scoped_session維護session對象
# 數據庫的增刪改查通過session對象來完成
self.engine_user = scoped_session(
sessionmaker(
bind=engine_user,
autocommit=False,
autoflush=True,
expire_on_commit=False
)
)
2.2.4)在Models/UserInfo/UserModel.py中配置模型信息,用於映射到數據庫中對應的表:
# coding=utf-8
from sqlalchemy import Table, MetaData
from sqlalchemy.ext.declarative import declarative_base
from Config.config_engine import engine_user
BaseModel = declarative_base()
def user_model(table_name):
class UserModel(BaseModel):
__tablename__ = table_name
metadata = MetaData(engine_user)
Table(__tablename__, metadata, autoload=True)
return UserModel
配置模型信息前,需要在數據庫中把表創建好,這是就需要寫sql語句創建表了。對於熟練sql的同學,寫sql語句應該不算什麼;對應不熟悉sql的同學,可能更習慣於django中那種創建表的方式。
2.2.5)以上都配置好以後,就可以在視圖中使用了
App/UserInfo/views.py:
# coding=utf-8
from tornado import web
from Models.UserInfo.BaseClass import BaseClass
from Models.UserInfo.UserModel import user_model
class UserInfoHandler(web.RequestHandler, BaseClass):
def get(self):
"""
獲取用戶信息
:return:
"""
# user_model中的參數對應數據庫中的表名
user_info = user_model("user_info")
# 獲取參數
user_id = self.get_query_argument("id")
# self.engine_user其實就是一個session對象;query()方法會返回一個query.Query對象,通過這個對象查詢數據庫
user_info_obj = self.engine_user.query(user_info).filter(user_info.id==user_id).first()
self.write(user_info_obj.name)
self.finish()
2.2.6)最後配置好url:
Urls/UserInfo.py:
# coding=utf-8
from App.UserInfo import views
user_urls = [
(r'/userinfo', views.UserInfoHandler),
]
application.py:
# coding=utf-8
from tornado import web
from Config.config_base import settings
from Urls.UserInfo import user_urls
from Urls.Shop import shop_urls
"""
路由配置
"""
class Application(web.Application):
def __init__(self):
urls = user_urls + shop_urls
super(Application, self).__init__(urls, **settings)
啓動服務後,就可以訪問了。
3.異步非阻塞請求
1)django
django中可以通過celery來實現異步任務,也可以使用asyncio和aiohttp實現異步。下面講一下celery的使用:
3.1.1)首先需要安裝 celery和 django-celery,使用pip安裝就行了;
3.1.2)然後在zsettings.py中進行如下配置:
在INSTALLED_APPS中加入djcelery。
import djcelery
# Celery便會去查看INSTALLD_APPS下包含的所有app目錄中的tasks.py文件,找到標記爲task的方法,將它們註冊爲celery task
djcelery.setup_loader()
BROKER_URL = 'redis://127.0.0.1:6379/2'
CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/3'
# 或者使用rabbitmq:
BROKER_URL = 'amqp://test:[email protected]:5672/testhost'
CELERY_RESULT_BACKEND = 'amqp://test:[email protected]:5672/testhost'
3.1.3)在需要使用異步的app中創建tasks.py文件,然後編輯該文件:
# coding=utf-8
import time
from celery import task
@task
def test(data):
"""
預處理
:param data:
:return:
"""
time.sleep(3)
return data
耗時的任務就可以放在使用@task修飾的函數中
3.1.4)在views.py中調用tasks.py中的函數
from rest_framework.response import Response
from .tasks import test
class CeleryTrainView(APIView):
def get(self, request):
try:
for i in range(0, 5):
ret = test.delay(str(i))
print("ret:", ret)
except Exception as e:
return Response(dict(msg=str(e), code=10001))
return Response(dict(msg="OK", code=10000))
上面的結果ret是一個AsyncResult對象,可以通過這個對象拿到保存在CELERY_RESULT_BACKEND中的結果。如果想立即得到結果,可以直接調用get()方法,但是這樣就會阻塞其他請求,直到結果返回:
from rest_framework.response import Response
from .tasks import test
class CeleryTrainView(APIView):
def get(self, request):
try:
for i in range(0, 5):
ret = test.delay(str(i))
print("ret:", ret.get())
except Exception as e:
return Response(dict(msg=str(e), code=10001))
return Response(dict(msg="OK", code=10000))
3.1.5)啓動celery
#先啓動服務器
python manage.py runserver
#再啓動worker
python manage.py celery worker
2)tornado
tornado中實現異步有回調和協程這兩種方式,這裏只舉一個協程實現異步的例子:
from tornado import web
from tornado import gen
from tornado.httpclient import AsyncHTTPClient
class AsyncHandler(web.RequestHandler):
@gen.coroutine
def get(self, *args, **kwargs):
client = AsyncHTTPClient()
url = 'http://ip.taobao.com/service/getIpInfo.php?ip=14.130.112.24'
# 根據ip地址獲取相關信息
resp = yield client.fetch(url)
data = str(resp.body, encoding="utf-8")
print("data:", data)
self.write(data)
self.finish()
或者像下面這樣,把獲取ip信息的部分封裝成一個函數:
from tornado import web
from tornado import gen
from tornado.httpclient import AsyncHTTPClient
class AsyncHandler(web.RequestHandler):
@gen.coroutine
def get(self, *args, **kwargs):
ip_info = yield self.get_ip_info()
self.write(ip_info)
self.finish()
@gen.coroutine
def get_ip_info(self):
client = AsyncHTTPClient()
url = 'http://ip.taobao.com/service/getIpInfo.php?ip=14.130.112.24'
resp = yield client.fetch(url)
data = str(resp.body, encoding="utf-8")
return data
也可以同時發起多個異步請求:
from tornado import web
from tornado import gen
from tornado.httpclient import AsyncHTTPClient
class AsyncHandler(web.RequestHandler):
@gen.coroutine
def get(self, *args, **kwargs):
ips = [
"14.130.112.24",
"14.130.112.23",
"14.130.112.22"
]
info1, info2, info3 = yield [self.get_ip_info(ips[0]), self.get_ip_info(ips[1]), self.get_ip_info(ips[2])]
self.write(info1)
self.write(info2)
self.write(info3)
self.finish()
@gen.coroutine
def get_ip_info(self, ip):
client = AsyncHTTPClient()
url = 'http://ip.taobao.com/service/getIpInfo.php?ip=' + ip
resp = yield client.fetch(url)
data = str(resp.body, encoding="utf-8")
return data
AsyncHTTPClient的fetch()方法有兩種調用方式,一種是像上面那樣只傳入一個url的字符串,另一種是接收一個HTTPRequest對象作爲參數,像下面這樣:
@gen.coroutine
def get_ip_info(self, ip):
client = AsyncHTTPClient()
url = 'http://ip.taobao.com/service/getIpInfo.php?ip=' + ip
header = {'Accept': 'application/json;charset=utf-8',
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'}
param1 = 'test'
http_request = HTTPRequest(url=url,
method='POST',
headers=header,
body=urlencode({'param1': param1}))
resp = yield client.fetch(http_request)
data = str(resp.body, encoding="utf-8")
return data
4.websocket的使用
1)django
django中使用websocket需要安裝第三方包dwebsocket。
2)tornado
tornado中實現websocket功能需要用到tornado.websocket模塊,主要有以下幾個方法:open(), write_message(), on_message(), on_close()
open(): 當websocket客戶端連接時所做的操作
write_message(): 使用這個方法向客戶端發送消息
on_message(): 接收並處理客戶端的消息
on_close(): websocket關閉連接時所作的操作
下面看一個例子:
views.py:
from tornado import websocket
class IndexHandler(web.RequestHandler):
def get(self, *args, **kwargs):
self.render("chat.html")
class ChatHandler(websocket.WebSocketHandler):
clients = set()
def open(self, *args, **kwargs):
self.clients.add(self)
for client in self.clients:
client.write_message("%s上線了" % self.request.remote_ip)
def on_message(self, message):
for client in self.clients:
client.write_message("%s: %s" % (self.request.remote_ip, message))
def on_close(self):
self.clients.remove(self)
for client in self.clients:
client.write_message("%s下線了" % self.request.remote_ip)
def check_origin(self, origin):
"""
用於處理跨域問題
:param origin:
:return:
"""
return True
路由:
# coding=utf-8
from App.UserInfo import views
user_urls = [
(r'/index', views.IndexHandler),
(r'/chat', views.ChatHandler),
]
chat.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>聊天室</title>
</head>
<body>
<div id="content" style="height: 500px;overflow: auto;"></div>
<div>
<textarea id="msg"></textarea>
<a href="javascript:;" onclick="sendMsg()">發送</a>
</div>
<script src="{{ static_url('js/jquery.min.js') }}"></script>
<script type="text/javascript">
var ws = new WebSocket("ws://192.168.1.104:8001/chat");
ws.onmessage = function (data) {
$("#content").append("<p>"+ data.data +"</p>")
};
function sendMsg() {
var msg = $("#msg").val();
if (msg) {
ws.send(msg);
}
}
</script>
</body>
</html>
上面一個例子通過websocket實現了簡單的聊天室功能。
以上就簡單的比較了django和tornado幾個方面的不同,它們各有優缺點,實際工作中可以根據不同的需求選擇不同的框架進行開發。
如果想了解如何在tornado中使用tcpserver,可以看一下這篇博客:
tornado中tcpserver和tcpclient的使用