建議去原文看,這排版不是太好
文章目錄
1.簡介
Tornado是一個python語言的web服務框架,它是基於社交聚合網站FriendFeed的實時信息服務開發而來的。
2007年由4名Google前軟件工程師一起創辦了FriendFeed,旨在使用戶能夠方便地跟蹤好友在Facebook和Twitter等多個社交網站上的活動。結果兩年後,Facebook宣佈收購FriendFeed
Tornado使FriendFeed使用的可擴展的非阻塞Web服務器及其相關工具的開源版本,這個Web框架看起來有些像web.py或 Google的webapp,不過爲了更加有效地利用非阻塞服務器環境,Tornado這個Web框架還包含了一些相關的有用工具和優化。
與其他web框架一些區別
- 非阻塞式的服務器
速度相當快,使用了epoll非阻塞的方式,每秒可以處理數以千計的連接。
- 單進程單線程異步IO的網絡模型
特點
- 輕量級Web框架
- 異步非阻塞IO處理方式(epoll)
單進程單線程異步IO的網絡模式, epoll的異步網絡IO
- 出色的抗負載能力
- 不依賴多進程或多線程(協程機制)
- WSGI全棧替代產品
WSGI把應用(Application)和服務器(Server)結合起來,Tornado既可以是WSGI應用也可以是WSGI服務。
既是WebServer也是WebFramework
- 支持長連接
- AysncIo
- http異步客戶端AsyncHttpClient
常用tornado參考doc
2.架構
可以先跳過架構這塊介紹,到安裝使用。(使用過了tornado,再來看整體架構就清晰容易多了)
2-1.分層
Tornado不僅僅是一個Web框架,它完整地實現了HTTP服務器和客戶端,再此基礎上提供了Web服務,它可分爲四層:
- Web框架:最上層,包括處理器、模板、數據庫連接、認證、本地化等Web框架所需功能。
- HTTP/HTTPS層:基於HTTP協議實現了HTTP服務器和客戶端
- TCP層:實現TCP服務器負責數據傳輸
- Event層:最底層、處理IO事件
注意顏色分層
- httpserver: 服務於web模塊的一個簡單的HTTP服務器的實現
Tornado的HTTPConnection類用來處理HTTP請求,包括讀取HTTP請求頭、讀取POST傳遞的數據,調用用戶自定義的處理方法,以及把響應數據寫給客戶端的socket。
- iostream: 對非阻塞式的socket的封裝以便於常見讀寫操作
爲了在處理請求時實現對socket的異步讀寫,Tornado實現了IOStream類用來處理socket的異步讀寫。
- ioloop 核心的I/O循環
Tornado爲了實現高併發和高性能,使用了一個IOLoop事件循環來處理socket的讀寫事件,IOLoop事件循環是基於Linux的epoll模型,可以高效地響應網絡事件,這是Tornado高效的基礎保證。
2-2.模塊
Tornado是一個輕量級框架,它的模塊不多最重要的模塊是web,web模塊包含了Tornado大部分主要功能的Web框架,其他模塊都是工具性質的,以便讓Web模塊更加有用。
Core Web Framework 核心Web框架
- tornado.web 包括Web框架大部分主要功能,包括RequestHandler和Application類。
- tornado.httpserver一個無阻塞HTTP服務器的實現
- tornado.template模板系統
- tornado.escape HTML、JSON、URLs等編碼解碼和字符串操作
- tornado.locale國際化支持
Asynchronous Networking 異步網絡底層模塊
- tornado.ioloop 核心IO循環
- tornado.iostream對非阻塞的Socket的簡單封裝以方便常用讀寫操作
- tornado.httpclient無阻塞的HTTP服務器實現
- tornado.netutil網絡應用的實現主要是TCPServer類
Integration With Other Services 系統集成服務
- tornado.auth 使用OpenId和OAuth進行第三方登錄
- tornado.databaseMySQL服務端封裝
- tornado.platform.twisted在Tornado上運行Twisted實現的代碼
- tornado.websocket實現和瀏覽器的雙向通信
- tornado.wsgi其他Python網絡框架或服務器的相互操作
Utilities 應用模塊
- tornado.autoload產生環境中自動檢查代碼更新
- tornado.gen基於生成器的接口,使用該模塊 保證代碼異步運行。
- tornado.httputil分析HTTP請求內容
- tornado.options解析終端參數
- tornado.process多進程實現的封裝
- tornado.stack_context異步環境中對回調函數上下文保存、異常處理
- tornado.testing單元測試
2-3.請求流程
注意請求和返回的流程(顏色區別,以IO Loop爲起點)
3.安裝
注意版本Tornado和python的版本:
- Tornado4.3 可以運行在Python2.6、2.7、3.2+
- Tornado6.0需要Python3.5.2+
3-1.python環境安裝
python環境可以分爲本地環境、虛擬環境、遠程環境
- 本地環境
本機安裝個python,然後配置下環境變量。
- 虛擬環境
借用一些軟件(比如:Anaconda、Virtualenv/Virtualenvwrapper),安裝虛擬環境,可以達到隔離效果,多個python在一臺機器上也不會衝突
- 遠程環境
非本機,一臺遠程機器上python環境
我使用的是Anaconda,如何使用和安裝可以參照這篇blog。 python環境安裝
至於virtualenv和本機安裝環境,自行用搜索引擎解決下
3-2.開發工具
主流的python開發工具
- pycharm
- vscode
- eclipse
我使用的是pycharm,如何安裝也不多介紹了。裏面有下載地址:python環境安裝
3-3.pip使用
pip如何使用,如何切換國內網,也都看這篇blog吧。python環境安裝
3-4.安裝tornado
# 使用anaconda創建一個tornado虛擬環境
conda create --name iworkh-tornado python=3.7
# 查看有哪些環境
conda info -e
# 切換到tornado虛擬環境
activate iworkh-tornado
# pip安裝tornado
pip install tornado
# 或使用condo安裝tornado
conda install tornado
3-5.創建項目
pycharm創建項目
因爲前異步使用anaconda創建一個tornado虛擬環境,所以就使用已存在的環境了。
當然如果前面沒有自己創建虛擬環境,也可以使用pycharm裏的
new environment using XXX
(選中虛擬化工具),來創建新的。
至此,tornado的開發環境基本ok了,如果有問題,可在留言區留言,或者QQ聯繫我。
4.tornado優勢
4-1.web框架比較
提到python web框架,那麼就想到了Django和Flask,那麼他們有啥區別呢?
可以讀下這幾篇blog
總結以幾點下:
- 重量級
django比較重量級;flask和tornado都比較輕量級
- 開發效率
django大而全的框架,效率高;
- 併發能力
django和flask同步,能力查;tornado異步框架,性能相對好
- 長連接
tornado適合用於開發長連接多的web應用。比如股票信息推送、網絡聊天等。
- 數據庫與模板處理性能
Tornado與Flask旗鼓相當;django慢
- 部署方面
tornado即使web框架,也是web服務;flask和tornado知識web框架,需要藉助web服務器來部署
4-2.如何高併發
- 異步非阻塞IO(基於epoll事件驅動)
- 通過協程來實現高併發處理
4-3.建議
- 不在tornado中使用大量的同步IO操作 (發揮不了tornado的特性)
- 不要將耗時的操作都往線程池中扔 (這並不能提高tornado多大的性能)
- tornado的線程和協程是兩個概念
- 編程時使用asnyc和await操作,而不建議coroutine協程裝飾器和yield
coroutine是一個gen方式歷史過濾方案,在python3.x後,已經使用了asnyc
5.tornado異步編程
我們都知道cpu的處理能力要遠遠大於IO的處理能力,而IO是阻塞操作,即io阻塞了,cpu即在休息,在虛度時間,在cpu資源,(浪費是可恥的,我們要利用最大化)。
當然不只本地磁盤IO,我們訪問網絡,常使用的requests
和urllib
的庫都是同步的。
5-1.io模型
io模型主要分爲同步/異步,阻塞/非阻塞
同步和異步:是相對於調用方而言的
阻塞和非阻塞: 是相對於被調用方而言的
譬如:用戶程序(用戶空間,A)調用了文件系統內容(內核空間,B),假設B操作比較耗時要10秒鐘。
對於A而言,是立刻得到B的返回結果呢,還是一直等着。等B處理完能夠通知A,還是A要一直在等着?(同步/異步)
對於B而言,接收到A的請求了,處理完後,如何能夠通知到A,不用限制着A,要一直等着?(阻塞/非阻塞)
阻塞
安裝下requests工具表
pip install requests
代碼
import requests
blogHtml = requests.get("https://iworkh.gitee.io/blog/").content.decode("utf8")
print(blogHtml)
requests.get
是阻塞,如果網不好或服務器響應很慢的話,那阻塞這回很耗時
非阻塞
import socket
# AF_INET:服務器之間網絡通信,SOCK_STREAM 流式socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 設置非阻塞
client.setblocking(False)
# 傳個tuple
address = "www.baidu.com"
try:
client.connect(("www.baidu.com", 80))
except BlockingIOError as e:
print("連接中...幹些其他事")
pass
while True:
try:
client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format("/", address).encode("utf8"))
print("連接成功")
break
except OSError as e:
pass
# 定義bytes
data = b""
while 1:
try:
rec = client.recv(512)
if rec:
data += rec
else:
print(data.decode("utf8"))
break
except BlockingIOError as e:
pass
setblocking
設置爲非阻塞的,然後後面一直使用while循環來處理,直到正常返回結果。(比太關注死循環的寫法,而要關注於非阻塞了)
5-2.io多路複用
IO多路複用:一個進程監控多個描述符fds,當描述符狀態爲read、write等狀態時,通知程序進行相應的操作。
select、poll和epoll都是多路複用機制。它們本質上也是同步IO,它們在讀寫過程是阻塞的。
而異步IO是非阻塞,由內核將數據從內核空間拷貝到用戶空間
像瞭解其原理德胡啊,強力建議去看篇blog
5-2-1.select
select函數監控3類文件描述符: readfds,writefds,exceptfds。
window和linux都支持,但是單進監控的文件描述符有限,linux一般爲1024。
- 沒有數據時,進程A被放到等待隊列,即cpu沒有處理進程A
- 當有數據來,socket會處於就緒狀態,將進程A放到cpu隊列中去工作
- 但是線程A不知道哪些socket是就緒的,所以只能全部遍歷一邊,去找到就狀態了。
5-2-3.poll
poll使用一個pollfd指針實現。pollfd監控even事件,雖們沒有最大限制,但是太多也影響性能。需要遍歷所有的描述符。
某時刻,文件描述符,處於就緒狀態很少。但是卻要遍歷所有描述符,因此當文件描述符很多時,性能會下降。
5-2-4.epoll
epoll對select和poll增強。是在2.6內核中提出的。沒有最大文件描述符的限制。
epoll使用一個文件描述符區管理多個描述符,將用戶的文件描述符event存在內核時間表中,這樣用戶空間和內核只要copy一次。
由於喚醒的進程,不知道哪些是就緒狀態,所以可以將就緒狀態的socket存下以便查找,不用都所有都遍歷一遍。
- 使用了eventpoll來記錄,監控進程和哪些socket是處於就緒狀態
- 當Socket接收到數據,中斷程序一方面修改 Rdlist,另一方面喚醒 eventpoll 等待隊列中的進程,進程A再次進入運行狀態
- 也因爲 Rdlist 的存在,進程 A 可以知道哪些 Socket 發生了變化。
5-2-5.回調異步
上面異步代碼,都是while和try來處理,這我們修改下使用select方式進行改造
import socket
from selectors import DefaultSelector, EVENT_WRITE, EVENT_READ
selector = DefaultSelector()
class CallBackRequestSite:
def get_url(self, url):
self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.client.setblocking(False)
self.host = url
self.data = b""
try:
self.client.connect((self.host, 80))
except BlockingIOError as e:
print("連接中...幹些其他事")
pass
selector.register(self.client.fileno(), EVENT_WRITE, self.connected)
def connected(self, key):
selector.unregister(key.fd)
self.client.send("GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\n\r\n".format("/", self.host).encode("utf8"))
selector.register(self.client.fileno(), EVENT_READ, self.read_data)
def read_data(self, key):
recv_data = self.client.recv(1024)
if recv_data:
self.data += recv_data
else:
selector.unregister(key.fd)
print(self.data.decode("utf8"))
def loop():
while 1:
# 根據系統是否支持,使用epoll還是select,優先epoll。默認阻塞,有活動連接就返回活動的連接列表
ready = selector.select()
for key, mask in ready:
callback = key.data
callback(key)
if __name__ == '__main__':
demo = CallBackRequestSite()
demo.get_url("www.baidu.com")
loop()
上面方式是通過回調函數的方式實現的異步操作,雖然異步了,但是回調方式的缺點也很明顯
回調方式的缺點
- 回調太多,會導致調用鏈太深(回調地獄)
- 不便於閱讀和維護
5-3.協程
協程(Coroutines)是一種比線程更加輕量級的存在,正如一個進程可以擁有多個線程一樣,一個線程可以擁有多個協程。
進程和線程是由系統控制,而協程是由程序自己控制的。好處是性能大幅度的提升,因爲不會像線程切換那樣消耗資源。
一個進程可以包含多個線程,一個線程也可以包含多個協程。一個線程的多個協程的運行是串行的。當一個協程運行時,其它協程必須掛起。
多核CPU,多個進程或一個進程內的多個線程是可以並行運行的,但一個線程內協程卻絕對是串行的,無論CPU有多少個核。
如對進程、線程、協程不太清除的閱讀這篇文件 進程、線程、協程
5-3-1.yield生成器
協程的一個重要基礎就是:程序運行一個協程能夠暫停,去運行另外的協程。(如何暫停?纔是關鍵)
yield就是可以滿足程序(函數/方法)執行時,暫停。
def yield_method():
print("do start...")
yield 1
res2 = yield 2
print("do c...res2:{}".format(res2))
yield 3
這是最簡單的函數中使用yield的例子,resp = yield_method()
是一個generator
生成器。
next(resp)
: 可以使得程序從一個yield到下一個yield的執行。(當到最後一個yield時,再執行next會報錯)send
: 可以發送參數給之前yield,並執行到下一個yield- foreach:一般都使用foreach來處理
不懂的可以閱讀下這篇文章 python中yield的用法詳解
def yield_method():
print("do start...")
yield 1
print("do a...")
res1 = yield 2
print("do b...res1: {}".format(res1))
res2 = yield 3
print("do c...res2:{}".format(res2))
yield 4
def call_hand():
resp = yield_method()
print(next(resp)) # 運行到 yield 1處,返回 1
print(resp.__next__()) # 從yield 1開始運行,到 yield 2處,返回 2
print(resp.send("got it")) # 從yield 2開始運行,並把send值給yield 2的變量,到 yield 3處,返回 3
print(next(resp)) # 從yield 3開始運行,到 yield 4處,返回 4
def call_for():
for item in yield_method():
print(item)
if __name__ == '__main__':
print(yield_method())
print("*" * 30)
call_hand()
print("*" * 30)
call_for()
5-3-2.異步
在tornado中,定義協程代碼有多種方式
- @coroutine裝飾器(裝飾器又分多家的,tornado和python都有)(這不推薦使用了)
- async方式 (推薦使用)
直接上代碼,裏面有註釋,具體自己悟
import asyncio
from asyncio import coroutine as pycoroutine
from time import sleep, time
from tornado.gen import coroutine as tcoroutine
async def async_method():
await asyncio.sleep(2)
print("do a...")
await await_sleep_one_min()
print("do b...python的async和await")
await t_yield_sleep_one_min()
print("do c...tornado裝飾器")
await py_yield_from_sleep_one_min()
print("do d...python裝飾器")
await asyc_sleep_one_min()
print("do e...")
# 不推薦使用, tornado裝飾器方式
@tcoroutine
def t_yield_sleep_one_min():
print("等了又等--1")
yield sleep(1)
print("等了又等--2")
yield sleep(1)
print("等了又等--3")
yield sleep(1)
# 不推薦使用,python裝飾器方式,python的舊語法
@pycoroutine
def py_yield_from_sleep_one_min():
yield from asyncio.sleep(1)
# 推薦使用(async與await組合,因爲asyncio.sleep也是個異步代碼,需要用await)
async def await_sleep_one_min():
await asyncio.sleep(1)
# 推薦使用(async單獨使用,因爲sleep不是個異步方法,不需要用await)
async def asyc_sleep_one_min():
print('asyc_sleep_one_min----waiting')
sleep(1)
if __name__ == '__main__':
start = time()
asyncio.run(async_method())
end = time()
print('cost time = ' + str(end - start))
簡單總結下:
- 協程定義可以用裝飾器或者async修飾(推薦async),後面只說async形式了。
- 協程A調用其他協程B(A和B函數都用async),協程A裏調用協程B的方法加使用
await
關鍵字,當然普通函數(非協程)不需要加await的
5-3-3.執行
執行協程方法,也有多種:tornado的IOLoop和python的asyncio
python方式
方式一:一直run
import asyncio
if __name__ == '__main__':
start = time()
# 一直run
asyncio.ensure_future(async_method())
asyncio.get_event_loop().run_forever()
end = time()
print('cost time = ' + str(end - start))
方式二:run完就結束
asyncio.run(async_method())
方式三:run完就結束
asyncio.get_event_loop().run_until_complete(async_method())
tornado方式
# 通過tornado的IOLoop來run (執行某個協程後停止事件循環)
IOLoop.current().run_sync(async_method)
5-4.AsnycHttpClient
同步方式HTTPClient
from tornado import httpclient
def get_url(url):
http_client = httpclient.HTTPClient()
resp = http_client.fetch(url)
print(resp.body.decode("utf8"))
if __name__ == '__main__':
web_site = "https://iworkh.gitee.io/blog/"
get_url(web_site)
異步方式AsnycHttpClient
import asyncio
from tornado import httpclient
async def async_get_url(url):
http_client = httpclient.AsyncHTTPClient()
resp = await http_client.fetch(url)
print(resp.body.decode("utf8"))
if __name__ == '__main__':
web_site = "https://iworkh.gitee.io/blog/"
asyncio.run(async_get_url(web_site))
6.tornado web基礎
tornado.web
提供了一種帶有異步功能並允許它擴展到大量開放連接的簡單的web框架, 使其成爲處理長連接(long polling) 的一種理想選擇.
6-1.簡單示例
import time
from tornado import web
from tornado.ioloop import IOLoop
class HelloWorldHandlerOne(web.RequestHandler):
async def get(self, *args, **kwargs):
# 別在handler中寫同步的代碼,會被阻塞的
time.sleep(5)
self.write("hello world---one")
class HelloWorldHandlerTwo(web.RequestHandler):
def get(self, *args, **kwargs):
self.write("hello world---two")
url_map = [
("/test1", HelloWorldHandlerOne),
("/test2", HelloWorldHandlerTwo)
]
if __name__ == '__main__':
app = web.Application(url_map, debug=True)
app.listen(8888)
IOLoop.current().start()
幾點說明
- handler類要繼承
web.RequestHandler
,然後重寫rest一些接口(get,post,put,delete等) - 啓動類,使用了
web.Application
,傳了參數url和handler對應關係。debug參數 - 在handler的方法裏不要調用同步的方法,這樣會阻塞請求。
即便請了不同的接口(test1和test2),由於test1的handler中
time.sleep(5)
,先請求test1,後請求test2,test2也會等test1響應完,纔會響應test2(單線程的)。
debug爲true
- debug設置爲true,修改了代碼,不用重啓服務
- idea的
Run
啓動:後臺啓動。如果關閉需要控制面板後臺關閉 - idea的
Debug
啓動:修改內容,會自動停了。得再次debug啓動 - 命令行啓動:後臺啓動。
ctr+c
可以退出
測試url
- http://localhost:8888/test1
- http://localhost:8888/test2
6-2.url匹配
url匹配主要是對於python的表達式的使用,如不瞭解可以先去了解下 菜鳥教程-python-正則表達式
from tornado import web, ioloop
class IndexHandler(web.RequestHandler):
async def get(self, *args, **kwargs):
self.write("首頁")
class UserHandler(web.RequestHandler):
async def get(self, name, *args, **kwargs):
self.write("hello world, {}".format(name))
class ProductHandler(web.RequestHandler):
def get(self, name, count, *args, **kwargs):
self.write("購買{}個{}".format(count, name))
class DivHandler(web.RequestHandler):
# one,two這個要跟url裏的(?P<one>\d+)/(?P<two>\d+)裏的名稱匹配
def get(self, one, two, *args, **kwargs):
try:
result = int(one) / int(two)
self.write("{}/{}={}".format(one, two, result))
except ZeroDivisionError:
# 發生錯誤,會跳轉
self.redirect(self.reverse_url('error_page', 500))
pass
class ErrorHandler(web.RequestHandler):
def get(self, code, *args, **kwargs):
self.write("發生錯誤:error_code: {}".format(code))
url_map = [
("/user/(\w+)/?", UserHandler),
("/product/(\w+)/(\d+)/?", ProductHandler),
("/calc/div/(?P<one>\d+)/(?P<two>\d+)/?", DivHandler),
web.URLSpec("/index/?", IndexHandler, name="index"),
web.URLSpec("/error/(?P<code>\d+)/?", ErrorHandler, name="error_page")
]
if __name__ == '__main__':
app = web.Application(url_map, debug=True)
app.listen(8888)
print('started...')
ioloop.IOLoop.current().start()
幾點說明
/?
表示0或1個/
,即url最後可以有/
也可以沒有/
- url定義,可以使用tuple簡單實現,也可以使用
web.URLSpec
來創建對象,可以指定一些參數(url,handler,kwargs,name) (?P<xxx>\d+)
,表示取一個組名,這個組名必須是唯一的,不重複的,沒有特殊符號,然後跟參數里名稱要一樣redirect
重定向,reverse_url
根據名稱類獲取url
測試url
- http://localhost:8888/index/
結果:
首頁
- http://localhost:8888/user/iworkh
結果:
hello world, iworkh
- http://localhost:8888/product/apple/3
結果:
購買3個apple
- http://localhost:8888/calc/div/4/2
結果:
4/2=2.0
- http://localhost:8888/calc/div/4/0
結果:
發生錯誤:error_code: 500
- http://localhost:8888/errpr/404
結果:
發生錯誤:error_code: 404
6-3.動態傳參
動態傳參:將一些配置參數通過命令行、配置文件方式動態傳給程序,而不是代碼寫死。增加靈活性。
官網關於options的介紹 options-doc
動態傳參主要使用的tornado.options
某塊,使用步驟:
- 先定義哪些參數
options.define()
- 從命令行或文件獲取參數值
options.parse_command_line()
,options.parse_config_file("/server.conf")
- 使用參數
options.xxx
6-3-1.命令方式
直接上代碼
from tornado import options, web, ioloop
options.define("port", default="8888", type=int, help="端口號")
options.define("static_path", default="static", type=str, help="靜態資源路徑")
class IndexHandler(web.RequestHandler):
async def get(self):
await self.finish('welcome to iworkh !!!')
url_map = [
("/?", IndexHandler)
]
setting = {
'debug': True
}
if __name__ == '__main__':
# 命令行方式
options.parse_command_line()
app = web.Application(url_map, **setting)
app.listen(options.options.port)
ioloop.IOLoop.current().start()
這注意: url_map是個list,而setting是個dic,去
web.Application
的初始化方法中就可以看到需要的參數類型
啓動命令
python option_demo.py --port=8080
通過
--port
指定啓動端口
測試url
- http://localhost:8080/
6-3-2.文件方式
將上面啓動main代碼修改爲以下:
if __name__ == '__main__':
# 配置文件方式
options.parse_config_file('config.ini')
app = web.Application(url_map, **setting)
app.listen(options.options.port)
ioloop.IOLoop.current().start()
配置文件內容
port = 8889
就加了端口號
測試url
- http://localhost:8889/
6-4.handler
因爲後面內容,請求的方式不僅僅是get,還有post等,所以使用Postman
工具來請求
Postman很簡單,自行下載使用下,這就不多介紹了
handler裏內容和細節相當得多,具體可以參照官網 RequestHandler-doc
我們主要從以下幾個方面入手
- input (前臺傳來的參數,如何解析.)
- process (RequestHandler常用的方法get,post,put,delete)
- output (後臺處理後,如何返回給前臺)
- 常用的handler(RequestHandler,ErrorHandler,FallbackHandler,RedirectHandler,StaticFileHandler)
- 還有cookie和application,這部分看下官網
6-4-1.input
常用的函數
`get_argument`,`get_arguments`: 從post的form中解析參數,如果form中沒有,那會從url中解析
`get_query_argument`,`get_query_arguments`: 從url中解析參數
`get_body_argument`,`get_body_arguments`: json格式數據解析
`request`
`path_args`
`path_kwargs`
注意函數名有s和沒有s的區別:
- 有s表示接受的是一個數組,即使一個數據,也會轉爲數組形式.
- 沒有s的,但是傳了多個值(比如:http://localshot:8888/user?name=lilei&&name=wangwu),最後一個會生效(name=wangwu)
結論:
方法 | 傳參 |
---|---|
get_query_argument(s) | 參數在url中 |
get_argument(s) | 參數在form中,沒有頭信息,form中沒有還可以從url中獲取 |
get_body_argument(s) | 參數在form中,帶有頭信息 |
request.body | 參數是json |
演示代碼
import json
from tornado import web, ioloop
class UrlParamHandler(web.RequestHandler):
async def get(self):
name = self.get_query_argument("name")
age = self.get_query_argument("age")
self.write("name: {}, age: {}".format(name, age))
self.write('<br/>')
names = self.get_query_arguments("name")
ages = self.get_query_arguments("age")
self.write("names: {}, ages: {}".format(names, ages))
class FormParamHandler(web.RequestHandler):
async def post(self):
name = self.get_argument("name")
age = self.get_argument("age")
self.write("name: {}, age: {}".format(name, age))
self.write('<br/>')
names = self.get_arguments("name")
ages = self.get_arguments("age")
self.write("names: {}, ages: {}".format(names, ages))
class JsonWithFormHeadersParamHandler(web.RequestHandler):
async def post(self):
name = self.get_body_argument("name")
age = self.get_body_argument("age")
self.write("name: {}, age: {}".format(name, age))
class JsonParamHandler(web.RequestHandler):
async def post(self):
parm = json.loads(self.request.body.decode("utf8"))
name = parm["name"]
age = parm["age"]
self.write("name: {}, age: {}".format(name, age))
url_handers = [
("/url", UrlParamHandler),
("/form", FormParamHandler),
("/jsonwithformheaders", JsonWithFormHeadersParamHandler),
("/json", JsonParamHandler)
]
if __name__ == '__main__':
app = web.Application(url_handers, debug=True)
app.listen(8888)
print("started...")
ioloop.IOLoop.current().start()
測試url
測試工具Postman,當然也可以使用python的requests
來請求
1. 參數在url中,get請求
代碼請求接口方式測試
import requests
url="http://localhost:8888/url?name=zhangsan&&age=20"
resp = requests.request('GET', url).content.decode("utf8")
print(resp)
postman或瀏覽器方式測試
- http://localhost:8888/url?name=zhangsan&&age=20
結果:
name: zhangsan, age: 20
names: [‘zhangsan’], ages: [‘20’]
- http://localhost:8888/url?name=zhangsan&&age=20&&name=lisi&&age=28
結果:
name: lisi, age: 28
names: [‘zhangsan’, ‘lisi’], ages: [‘20’, ‘28’]
由上面結果,可以看出函數名有s和沒s的區別了,下面就其他方法就演示了,因爲有s的,就不演示了跟這類似.
個人覺得,不用s,傳多個值,爲啥不直接傳數組了.特別後面是json格式交互的時候,多個值的場景完全可以數組作爲值來傳遞
2. 參數在form中,沒有請求頭,post請求
代碼請求接口方式測試
import requests
url = "http://localhost:8888/form"
data = {
"name": "zhangsan",
"age": 20
}
resp = requests.request('POST', url, data=data).content.decode("utf8")
postman方式測試
- http://localhost:8888/form
結果:
name: lisi, age: 28
names: [‘zhangsan’, ‘lisi’], ages: [‘20’, ‘28’]
3. 參數在from中,並含有請求頭Content-Type:application/x-www-form-urlencoded
,post請求
代碼請求接口方式測試
import requests
url = "http://localhost:8888/jsonwithformheaders"
headers = {"Content-Type": "application/x-www-form-urlencoded;"}
data = {
"name": "zhangsan",
"age": 20
}
resp = requests.request('POST', url, headers=headers, data=data).content.decode("utf8")
print(resp)
postman方式測試
- http://localhost:8888/jsonwithform
4. 參數是json,沒有頭,post請求
代碼請求接口方式測試
import requests
url = "http://localhost:8888/json"
data = {
"name": "zhangsan",
"age": 20
}
resp = requests.request('POST', url, json=data).content.decode("utf8")
print(resp)
注意這這參數傳的是
json=data
,而不是data=data
哦
postman方式測試
- http://localhost:8888/json
沒有頭,只能通過
request.body
來拿到參數,而且是bytes類型,需要decode後,json解析後得到dict
提示: 注意json值傳遞時候,有沒header,取值方式不同.結論如下:
方法 | 傳參 |
---|---|
get_query_argument(s) | 參數在url中 |
get_argument(s) | 參數在form中,沒有頭信息 |
get_body_argument(s) | 參數在form中,帶有頭信息 |
request.body | 參數是json |
6-4-2.process
注意: process裏函數的執行順序
initialize: 初始化,通過`URLSpec`可將參數傳遞到方法
prepare: 在get/post等操作之前,會調用
get/head/post/delete/patch/put/options: 一般業務邏輯處理
on_finish: 在out操作之後,會調用
直接上代碼
from tornado import web, ioloop
class ProcessDemoHandler(web.RequestHandler):
# initialize方法同步
def initialize(self, dbinfo):
print("initialize...")
self.dbinfo = dbinfo
print("數據庫host:{}".format(self.dbinfo['db_host']))
pass
async def prepare(self):
print("prepare...")
pass
async def get(self):
print("get...")
self.write("success!!!")
pass
def on_finish(self):
print("finish...")
pass
init_param = {
'dbinfo': {
'db_host': 'localhost',
'db_port': 3306,
'db_user': 'root',
'db_password': '123',
}
}
url_handers = [
(r"/?", ProcessDemoHandler, init_param),
]
if __name__ == '__main__':
app = web.Application(url_handers, debug=True)
app.listen(8888)
print("started...")
ioloop.IOLoop.current().start()
需要關注以下幾點:
1.順序: initialize 👉 prepare 👉 get/post/put/delete/… 👉 on_finish
2.initialize和on_finish方法同步
3.如何將參數通過url_handers
傳給initialize方法
結果:
initialize...
數據庫host:localhost
prepare...
get...
6-4-3.output
set_status: 設置狀態碼
set_header
add_header
clear_header
write: 寫數據,可以write多次,放緩存中,而不會中斷當flush或者finish或者沒消息斷開時發送
flush: 刷新數據到客戶端
finish: 寫數據,寫完斷開了
render/render_string: 模板方式渲染輸出
redirect: 重定向
send_error/write_error: 發送錯誤
render_embed_js/render_embed_css: 嵌入的js和css
render_linked_js/render_linked_css: 鏈接的js和css
import asyncio
from tornado import web, ioloop
class OutputDemoHandler(web.RequestHandler):
async def get(self):
self.set_status(200)
self.write("error!!!")
self.write("warning!!!")
self.write("<br/>")
await self.flush()
await asyncio.sleep(5)
self.write("success!!!")
await self.finish("done")
url_handers = [
(r"/?", OutputDemoHandler),
]
if __name__ == '__main__':
app = web.Application(url_handers, debug=True)
app.listen(8888)
print("started...")
ioloop.IOLoop.current().start()
這裏主要使用了
write
,flush
以及finish
操作,其他前面用過就不多介紹了.
沒用過的render_xxx
形式的函數,在模板中使用的。
結果:
error!!!warning!!!
success!!!done
第一行先出來,應爲flush了,過了5秒後,第二行數據出來
6-5.setting
setting裏每項值類型都是dic
類型
配置主要分以下幾種類型
- General settings
- Authentication and security settings:
- Template settings
- Static file settings
這不會全部介紹,只介紹下常用的,在實際開發中,去官網查閱.
官網setting配置項 setting配置
打開後,直接搜索關鍵字
settings
,就能定位到
常用配置
debug
是否開啓debug模式
default_handler_class
當url沒有配置到handler時,默認處理器.
static_path
靜態資源路徑
static_url_prefix
靜態資源前綴
static_handler_class
靜態資源handler(默認是
tornado.web.StaticFileHandler
)
靜態資源代碼演示
from tornado import web, ioloop
class SettingDemoHandler(web.RequestHandler):
async def get(self):
await self.finish("success")
url_handers = [
(r"/?", SettingDemoHandler),
]
settings={
'debug': True,
'static_path': 'public/static',
'static_url_prefix': '/static/'
}
if __name__ == '__main__':
app = web.Application(url_handers, **settings)
app.listen(8888)
print("started...")
ioloop.IOLoop.current().start()
在相對路徑下,創建
public/static/images
文件夾,並放一張圖片進去
測試url
- http://localhost:8888/static/images/moon.jpg
端口號後面的前綴url緊跟的路徑,要跟
static_url_prefix
配置一致
6-6.模板
模板就不多介紹了,工作中我們一般開發都前後端分離,因此這就不過多糾纏了。(單爲了文章的完整性,我還是寫了個簡單例子)
前端:react、vue等前端框架
後端:java、go、python等語言編寫的web框架跟前端進行數據交互
有興趣的可以看下官網:模板-docs
主要包含以下幾點
1.普通字符串模板,加載使用
2.文件模板,加載使用
3.常用的模板語法 {{ data }},{% set ...%},{% raw %}
4.導入python,調用函數,循環輸出
5.模板繼承 {% extends "xxx.html" %}
6.UIModule使用 {% module Template("foo.html", arg=42) %}
6-6-1.頁面結構
基本結構
6-6-1.結果
結果
6-6-2.代碼
代碼主要分模板、UIMoudle、orderHandler、啓動類
啓動類和handler
from typing import Optional
from tornado import web, ioloop
class OrderHandler(web.RequestHandler):
def get(self):
data = {
'title': '書本列表',
'orderList': [
{'id': 1, 'name': 'java', 'price': 80, 'count': 1,
'link_to': '<a href="http://www.baidu.com">查看</a>'},
{'id': 2, 'name': 'springboot', 'price': 100, 'count': 2,
'link_to': '<a href="http://www.baidu.com">查看</a>'},
{'id': 3, 'name': 'springcloud', 'price': 120, 'count': 1,
'link_to': '<a href="http://www.baidu.com">查看</a>'},
]
}
self.render("tp_order.html", **data)
class FooterUIMoudle(web.UIModule):
def render(self):
return self.render_string("uimodules/tp_footer.html")
def embedded_css(self) -> Optional[str]:
css = '''
.content { background: gray}
a {text-decoration-line: none}
'''
return css
url_handlers = [
(r"/order", OrderHandler)
]
settings = {
"debug": True,
"template_path": 'public/templates',
"static_path": 'public/static',
"static_url_prefix": "/static/",
"ui_modules": {
'FooterUIMoudle': FooterUIMoudle
}
}
if __name__ == '__main__':
app = web.Application(url_handlers, **settings)
app.listen(8888)
print("started successfully")
ioloop.IOLoop.current().start()
settings裏定義
ui_modules
,template_path
,static_path
注意給模板傳參數的類型,在目錄中也需要跟其對於方式取出值
tp_base.html模板
相對啓動類路徑:public/templates/tp_base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>商城</title>
<link rel="stylesheet" type="text/css" href="{{ static_url('css/base.css') }}">
{% block custom_css %}
{% end %}
</head>
<body>
<div class="header">
{% module Template("uimodules/tp_header.html") %}
</div>
<div class="content">
{% block order_html_block%}
<h2>訂單</h2>
{% end %}
</div>
{% module FooterUIMoudle() %}
{% block custom_js %}
{% end %}
</body>
</html>
裏面使用UIMoudle、繼承等手段
tp_base.html模板
相對啓動類路徑:public/templates/tp_base.html
{% extends 'tp_base.html' %}
{% block order_html_block %}
<div class="center">
<table style="margin: 0 auto">
<caption>{{ title }}</caption>
<thead>
<tr>
<td>id</td>
<td>名稱</td>
<td>價格</td>
<td>數量</td>
<td>詳情</td>
</tr>
</thead>
<tbody>
{% set total_price = 0 %}
{% for item in orderList %}
<tr>
<td>{{ item["id"] }}</td>
<td>{{ item["name"] }}</td>
<td>{{ item["price"] }}元</td>
<td>{{ item["count"] }}</td>
<td>{% raw item["link_to"] %}</td>
<div style="display: none">{{ total_price = total_price + item["price"] * item["count"] }}</div>
</tr>
{% end %}
</tbody>
</table>
總價格: {{ total_price }}
</div>
{% end %}
裏面使用繼承、循環、raw、賦值等手段
uimoudles下的tp_footer.html模板
相對啓動類路徑:public/templates/uimodules/tp_header.html
<h2>welcome to iworkh !!!</h2>
uimoudles下的tp_footer.html模板
相對啓動類路徑:public/templates/uimodules/tp_footer.html
<div class="footer">
<div style="padding: 0px 4px 10px; text-align: center; font-size: 12px;"><span>©2019 iworkh </span><a href="http://beian.miit.gov.cn/" target="_blank"><span style="margin-left: 10px; color: rgb(147, 147, 147);">蘇ICP備19061993號</span></a><a target="_blank" href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=32010602010683"><span style="margin-left: 10px; color: rgb(147, 147, 147);">蘇公網安備 32010602010683號</span></a></div>
</div>
7.mysql
python裏PyMySQL是同步,所以在tornado我們得用異步的,這介紹下aiomysql,它是基於PyMySQL的。用法差不錯,只不過實現了異步操作。
安裝依賴
pip install aiomysql
7-1.簡單示例
import asyncio
import aiomysql
async def test_example(loop):
pool = await aiomysql.create_pool(host='127.0.0.1', port=3306,
user='root', password='',
db='mysql', loop=loop)
async with pool.acquire() as conn:
async with conn.cursor() as cur:
await cur.execute("SELECT 42;")
print(cur.description)
(r,) = await cur.fetchone()
assert r == 42
pool.close()
await pool.wait_closed()
loop = asyncio.get_event_loop()
loop.run_until_complete(test_example(loop))
這是官網Github上給的示例,主要注意以下幾點
- async異步編寫
- 調用異步操作時,要使用await
- async with的用法,異步上下文管理器,接受會將對應的資源關閉的(異步上下文管理器指的是在enter和exit方法處能夠暫停執行的上下文管理器,
__aenter__
和__aexit__
方法。)
有興趣可以去了解下async with
和async for
,百度下隨便挑一篇閱讀下就明白了(基本都雷同的😊)
創建表
CREATE TABLE `tb_order` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`price` decimal(10,2) DEFAULT NULL,
`count` int(5) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
插入一條數據
INSERT INTO `iworkh_tornado`.`tb_order`(`id`, `name`, `price`, `count`)
VALUES (1, 'java', 80.00, 1);
7-1.查詢數據
使用get請求來請求rest接口,參數方法可以放在url中,也可以方法在form中 (通過get_argument獲取參數)
查詢邏輯
import aiomysql
from tornado import web, ioloop
class OrderHandler(web.RequestHandler):
def initialize(self, db_info) -> None:
self.db_info = db_info
async def get(self):
id = self.get_argument("id", None)
if not id:
raise Exception("id can not None")
db_id = db_name = db_price = db_count = None
async with aiomysql.create_pool(host=self.db_info["db_host"], port=self.db_info["db_port"],
user=self.db_info["db_user"], password=self.db_info["db_password"],
db=self.db_info["db_name"], charset=self.db_info["db_charset"]) as pool:
async with pool.acquire() as conn:
async with conn.cursor() as cur:
sql = "SELECT `id`, `name`, `price`, `count` FROM TB_ORDER WHERE id = {};".format(id)
print(sql)
await cur.execute(sql)
result = await cur.fetchone()
try:
if (result):
db_id, db_name, db_price, db_count = result
except Exception:
pass
self.write("success, {}, {}, {}, {}".format(db_id, db_name, db_price, db_count))
initParam = {
"db_info": {
'db_host': 'localhost',
'db_name': 'iworkh_tornado',
'db_port': 3306,
'db_user': 'iworkh',
'db_password': 'iworkh123',
'db_charset': 'utf8'
}
}
url_handlers = [
(r"/order/?", OrderHandler, initParam)
]
if __name__ == '__main__':
app = web.Application(url_handlers, debug=True)
app.listen(8888)
print("started successfully")
ioloop.IOLoop.current().start()
測試代碼
import requests
url = "http://localhost:8888/order"
data={
'id':'1'
}
resp = requests.get(url, data=data).content.decode("utf8")
print(resp)
7-2.插入數據
查詢邏輯
import aiomysql
from tornado import web, ioloop
class OrderHandler(web.RequestHandler):
def initialize(self, db_info) -> None:
self.db_info = db_info
async def post(self):
db_name= self.get_argument("name")
db_price= self.get_argument("price")
db_count= self.get_argument("count")
async with aiomysql.create_pool(host=self.db_info["db_host"], port=self.db_info["db_port"],
user=self.db_info["db_user"], password=self.db_info["db_password"],
db=self.db_info["db_name"], charset=self.db_info["db_charset"]) as pool:
async with pool.acquire() as conn:
async with conn.cursor() as cur:
sql = "INSERT INTO TB_ORDER (`name`, `price`, `count`) VALUES ('{}','{}','{}');"\
.format(db_name, db_price, db_count)
print(sql)
await cur.execute(sql)
await conn.commit()
self.write("save successfully")
initParam = {
"db_info": {
'db_host': 'localhost',
'db_name': 'iworkh_tornado',
'db_port': 3306,
'db_user': 'iworkh',
'db_password': 'iworkh123',
'db_charset': 'utf8'
}
}
url_handlers = [
(r"/order/?", OrderHandler, initParam)
]
if __name__ == '__main__':
app = web.Application(url_handlers, debug=True)
app.listen(8888)
print("started successfully")
ioloop.IOLoop.current().start()
測試代碼
import requests
url = "http://localhost:8888/order"
data = {
'name':'springboot',
'price': 100.0,
'count':2
}
resp = requests.post(url, data=data).content.decode("utf8")
print(resp)
7-3.更新數據
查詢邏輯
import aiomysql
from tornado import web, ioloop
class OrderHandler(web.RequestHandler):
def initialize(self, db_info) -> None:
self.db_info = db_info
async def put(self):
db_id= self.get_argument("id")
db_name= self.get_argument("name")
db_price= self.get_argument("price")
db_count= self.get_argument("count")
async with aiomysql.create_pool(host=self.db_info["db_host"], port=self.db_info["db_port"],
user=self.db_info["db_user"], password=self.db_info["db_password"],
db=self.db_info["db_name"], charset=self.db_info["db_charset"]) as pool:
async with pool.acquire() as conn:
async with conn.cursor() as cur:
sql = "UPDATE TB_ORDER SET `name` = '{}', `price` = '{}', `count` = '{}' WHERE `id` = {};"\
.format(db_name, db_price, db_count, db_id)
print(sql)
await cur.execute(sql)
await conn.commit()
self.write("save successfully")
initParam = {
"db_info": {
'db_host': 'localhost',
'db_name': 'iworkh_tornado',
'db_port': 3306,
'db_user': 'iworkh',
'db_password': 'iworkh123',
'db_charset': 'utf8'
}
}
url_handlers = [
(r"/order/?", OrderHandler, initParam)
]
if __name__ == '__main__':
app = web.Application(url_handlers, debug=True)
app.listen(8888)
print("started successfully")
ioloop.IOLoop.current().start()
測試代碼
import requests
url = "http://localhost:8888/order"
data = {
'id': 1,
'name':'java',
'price': 60.0,
'count':1
}
resp = requests.put(url, data=data).content.decode("utf8")
print(resp)
通過上面的aiomysl操作,雖然實現了異步操作,但是我們可以發現一些問題
- 線程池操作,每次一個請求來都創建了個線程池,然後關閉
- 原生sql操作,拼sql很累
- 查詢結果,解析很麻煩
- model對象和數據庫表關聯操作,進行查詢、更新、插入操作(有hibernate、jpa、Django orm等開發經驗着,應該明白ORM框架帶來的好處)
8.orm框架peewee
8-1.orm好處
我們通知之前對aiomysql對數據庫的操作,我們發現了一些問題,操作很麻煩。
那使用orm有啥好處呢?
- 操作更加方便,不用跟原生sql交互太多,便於維護。(特別寫sql少了
,
,'
等一些低價錯誤,很難發現,浪費開發時間) - orm可以屏蔽調底層數據(不管mysql、postgresql、sqlite等,都通過orm來操作,來適配)
- 還能防止sql注入
python領域常用的一些ORM框架
- Django’s ORM
- peewee
- SQLAlchemy
Django’s ORM
優點:
易用,學習曲線短
和Django緊密集合,用Django時使用約定俗成的方法去操作數據庫
缺點:
QuerySet速度不給力,會逼我用Mysqldb來操作原生sql語句。
不好處理複雜的查詢,強制開發者回到原生SQL
peewee
優點:
Django式的API,使其易用
輕量實現,很容易和任意web框架集成
aysnc-peewee異步操作
缺點:
不支持自動化schema遷移
不能像Django那樣,使線上的mysql表結構生成結構化的模型。
多對多查詢寫起來不直觀
SQLAlchemy
優點:
巨牛逼的企業級API,使得代碼有健壯性和適應性
靈活的設計,使得能輕鬆寫複雜查詢
缺點:
工作單元概念不常見
重量級 API,導致長學習曲線
8-2.peewee同步操作
數據操作,主要就增刪改查功能,下面分別演示如何使用。peewee官網doc
工作中使用前,最好把官網瀏覽一遍,掌握一些常用的api和細節
先pip安裝
pip install peewee
8-2-1.定義model
base_model.py
import datetime
from peewee import MySQLDatabase, Model, DateTimeField
db_info = {'host': '127.0.0.1', 'user': 'iworkh', 'password': 'iworkh123', 'charset': 'utf8', 'port': 3306}
db = MySQLDatabase('iworkh_tornado', **db_info)
class BaseModel(Model):
created_date = DateTimeField(default=datetime.datetime.now)
class Meta:
database = db
user_model.py
from peewee import CharField, DateField, IntegerField, BooleanField
from lesson05.models.base_model import *
class User(BaseModel):
username = CharField(column_name='username', unique=True, null=False, max_length=50, verbose_name="姓名")
birthday = DateField(column_name='birthday', null=True, default=None, verbose_name='生日')
phone = CharField(column_name='phone', max_length=11)
mail = CharField(column_name='mail', max_length=50, verbose_name='郵件')
gender = IntegerField(column_name='gender', null=False, default=1, verbose_name='姓別')
is_admin = BooleanField(column_name='is_admin', default=False, verbose_name='是否是管理員')
class Meta:
table_name = "T_USER"
from peewee import ForeignKeyField, CharField, IntegerField
from lesson05.models.base_model import BaseModel
from lesson05.models.user_model import User
class Pet(BaseModel):
user = ForeignKeyField(User, backref='pets')
name = CharField(index=True, column_name='name', max_length=50, verbose_name='名字')
age = IntegerField(column_name='age', verbose_name='年齡')
type = CharField(column_name='type', max_length=50, verbose_name='類型')
color = CharField(column_name='color', max_length=50, verbose_name='顏色')
description = CharField(column_name='description', max_length=500, verbose_name='描述')
class Meta:
table_name = "T_PET"
8-2-2.初始化表
import 省略
if __name__ == '__main__':
db.connect()
table_list = [User, Pet]
db.create_tables(table_list)
print("end...")
8-2-3.插入
插入數據,可以使用save
和create
方法來操作
- save: 對象操作
- create: 是類操作,看create函數上有
@classmethod
修飾符
import datetime
import 省略
userList = [
{'username': 'zhangsan', 'birthday': '1988-08-08', 'gender': 1, 'mail': '[email protected]', 'phone': '111',
'is_admin': False},
{'username': 'lisi', 'birthday': datetime.date(1999, 8, 8), 'gender': 1, 'mail': '[email protected]', 'phone': '222',
'is_admin': False},
{'username': 'wangwu', 'birthday': datetime.date(2025, 8, 8), 'gender': 0, 'mail': '[email protected]', 'phone': '333',
'is_admin': False},
{'username': 'admin', 'birthday': datetime.date(2000, 8, 8), 'gender': 1, 'mail': '[email protected]', 'phone': '444',
'is_admin': True}
]
def save():
user = User()
user.username = 'iworkh_save'
user.birthday = datetime.date(1988, 8, 8)
user.gender = 0
user.mail = "[email protected]"
user.phone = "888888888"
user.is_admin = True
# 使用對象調用方法
row = user.save()
print("save的返回值是值:{}".format(row))
def save_list():
for item in userList:
user = User(**item)
user.save()
def create():
user_dic = {
'username': 'iworkh_create2',
'birthday': datetime.date(1989, 8, 8),
'gender': 1,
'mail': '[email protected]',
'phone': '888888888',
'is_admin': False
}
# 使用類調用方法
row = User.create(**user_dic)
print("create的返回值值:{}".format(row))
if __name__ == '__main__':
save()
create()
save_list()
print("end...")
8-2-4.查詢
查詢比較重要,但是peewee的api使用,跟原生sql語法優點類似
sql: select * from user where username like %iworkh%
peewee寫法:User.select().where(User.username.contains(‘iworkh’))
import 省略
def select_all():
# modelSelect = User.select() # sql: select * from user
fields = [User.username, User.phone]
modelSelect = User.select(*fields) # sql: select username, phone from user
# 這還沒有立即執行,返回的是modelSelect對象,當for或者execute時纔會執行
for user in modelSelect:
print("{} --- {} --- {}".format(user.username, user.phone, user.is_admin))
def select_conditon():
# 根據id取記錄,如果id不存在,那麼轉化時候會報錯
user_res_get_by_id = User.get_by_id(1)
print("get_by_id 結果:{}".format(user_res_get_by_id.username))
user_res_get = User.get(User.id == 1)
print("user_res_get 結果:{}".format(user_res_get.username))
user_res_dic = User[1]
print("user_res_dic 結果:{}".format(user_res_dic.username))
print("*" * 50)
# select * from user where username like %iworkh%
modelSelect = User.select().where(User.username.contains('iworkh'))
for user in modelSelect:
print("{} --- {}".format(user.username, user.is_admin))
print("*" * 50)
# select * from user ordery by birthday desc
modelSelect_order = User.select().order_by(User.birthday.desc())
for user in modelSelect_order:
print("{} --- {}".format(user.username, user.birthday))
print("*" * 50)
# 按每頁3的大小分頁,取第2頁數據(從1開始計數)
modelSelect_pagable = User.select().paginate(page=2, paginate_by=3)
for user in modelSelect_pagable:
print("{} --- {}".format(user.username, user.id))
if __name__ == '__main__':
select_all()
select_conditon()
8-2-5.更新
更新很簡單,使用的是save
函數,當save裏傳id值時,則認爲是更新;id沒有值None時,則是插入操作
import 省略
def upate():
# 使用save方法即可更新,只要傳的值中還有id
user_res_get_by_id = User.get_by_id(1)
user_res_get_by_id.username = user_res_get_by_id.username + '_update'
# 從數據中取處記錄,並修改值後更新數據庫
user_res_get_by_id.save()
if __name__ == '__main__':
upate()
8-2-6.刪除
刪除記錄有兩種操作:
- 類操作,根據id刪除
- 對象操作,這個對象要id值,底層也是根據id來刪除記錄的
import 省略
def delete_by_id():
# 類操作
User.delete_by_id(3)
def delete_instance():
user = User()
user.id = 4
# 對象操作,recursive參數控制關聯數據是否刪除
user.delete_instance()
if __name__ == '__main__':
delete_by_id()
delete_instance()
print("end...")
8-2-7.技巧
技巧
peewee裏還有很其他知識點,除了閱讀官網提供的doc文檔外,還有一些地方值得我們開發者注意的,就是源碼下面的test和example模塊。
留個問題:
peewee裏transaction如何使用?(如何去官網doc和源碼下快速找到你需要的答案呢?)
8-3.peewee-async操作
tornado調用接口,那得用異步的,所以peewee的同步操作,在tornado中肯定玩不轉的。peewee-async官網doc
peewee-async的接口主要分兩部分
- 高級API (推薦使用)
主要通過manager來操作
- 低級API (不推薦使用)
主要通過peewee_async來操作
安裝
pip install --pre peewee-async
8-3-1.定義models
定義model還是跟peewee一樣,不過db的方式使用了peewee_async.MySQLDatabase
import datetime
import peewee_async
from peewee import Model, DateTimeField, TextField, CharField
db_info = {'host': '127.0.0.1', 'user': 'iworkh', 'password': 'iworkh123', 'charset': 'utf8', 'port': 3306}
db = peewee_async.MySQLDatabase('iworkh_tornado', **db_info)
class BaseModel(Model):
created_date = DateTimeField(default=datetime.datetime.now)
class Meta:
database = db
class BlogModel(BaseModel):
title = CharField(column_name='title', max_length=50)
content = TextField(column_name='content')
class Meta:
table_name = 't_blog'
8-3-2.基本操作
通過高級API的方式,進行增刪改查操作
import asyncio
import peewee_async
import 省略了models的引入
objects = peewee_async.Manager(db)
async def save():
blog = {'title': 'tornado入門', 'content': '詳細內容請看文章'}
await objects.create(BlogModel, **blog)
print("保存成功")
async def get_all():
all_blogs = await objects.execute(BlogModel.select())
for blog in all_blogs:
print("id: {}, 文章標題:{}".format(blog.id, blog.title))
async def delete():
blog = BlogModel()
blog.id = 1
await objects.delete(blog)
print("刪除成功")
def create_table():
BlogModel.create_table(True)
def drop_table():
BlogModel.drop_table(True)
async def test():
create_table()
await save()
print("*"*50)
await get_all()
print("*"*50)
await delete()
print("*"*50)
await get_all()
drop_table()
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(test())
print("end")
9.參考
- Tornado
- Tornado之架構概述圖
- Django、Flask與Tornado的性能對比
- 從django、flask、tornado的部署說起
- python框架(flask/django/tornado)比較
- python socket編程詳細介紹
- 🐂Epoll原理解析
- 進程、線程、協程
- python中yield的用法詳解
- aimysql-doc
- tornado-Cookie-CSRF-模板示例
10.推薦
整理不打字易,覺得有幫助就點個贊吧。