python是能夠建立GUI的,使用第三方庫通常是Tk、wxWidgets、Qt、GTK。 而python自帶的是支持Tk的Tkinter,咱們這裏就來用Tkinter來實現GUI。 其中Tkinter內置了訪問Tk的接口。Tk是一個圖形庫,支持多個操做系統,使用Tcl語言開發,Tk會調用本地操做系統提供的GUI接口,完成最終的GUI。
css
舉例以下:html
from tkinter import * class Application(Frame): def __init__(self, master=None): Frame.__init__(self, master) self.pack() self.createWidgets() def createWidgets(self): self.helloLabel = Label(self, text='Hello world!') self.helloLabel.pack() self.quitButton = Button(self, text='Quit', command=self.quit) self.quitButton.pack() app = Application() app.master.title('Hello world!') app.mainloop()
注意,這裏咱們須要引入tkinter的全部方法,因此是from tkinter import *, 而後建立一個Application類,__init__方法用於建立widgets。而在createWidgets中,咱們建立了一個label和一個button,且button的名稱是Quit,點擊後就會推出程序,運行文件後,效果以下所示:vue
另外,咱們還能夠對這個GUI程序改進一下,讓用戶輸入文字,而後點擊按鈕以後彈出對話框,以下:java
from tkinter import * import tkinter.messagebox as messagebox class Application(Frame): def __init__(self, master=None): Frame.__init__(self, master) self.pack() self.createWidgets() def createWidgets(self): self.nameInput = Entry(self) #用戶輸入內容做爲nameInput的值 self.nameInput.pack() #pack是打包的意思,實際上就是綁定、生效 self.alertButton = Button(self, text='Hello', command=self.hello) self.alertButton.pack() #能夠看到,每次咱們建立一個Lable或者Input或者alertButton時,須要pack def hello(self): name = self.nameInput.get() or 'world' messagebox.showinfo('Message', 'Hello, %s' % name) app = Application() app.master.title('Hello world!') app.mainloop()
如上所示。 下面,咱們還能夠作一個計算機,計算兩數字之和,以下所示:node
from tkinter import * import tkinter.messagebox as messagebox class Application(Frame): def __init__(self, master=None): Frame.__init__(self, master) self.pack() self.createWidgets() def createWidgets(self): self.num1Input = Entry(self) #用戶輸入內容做爲numInput的值 self.num1Input.pack() #pack是打包的意思,實際上就是綁定、生效 self.num2Input = Entry(self) self.num2Input.pack() self.alertButton = Button(self, text='兩數之和', command=self.calc) self.alertButton.pack() #能夠看到,每次咱們建立一個Lable或者Input或者alertButton時,須要pack self.quitButton = Button(self, text='退出', command=self.quit) self.quitButton.pack() def calc(self): num1 = int(self.num1Input.get()) or 0 num2 = int(self.num2Input.get()) or 0 messagebox.showinfo('Message', '和爲: %s' % (num1 + num2)) app = Application() app.master.title('求兩數之和') app.mainloop()
from tkinter import * import tkinter.messagebox as messagebox class Application(Frame): def __init__(self, master=None): Frame.__init__(self, master) self.pack() self.createWidgets() def createWidgets(self): self.num1Input = Entry(self) #用戶輸入內容做爲numInput的值 self.num1Input.pack() #pack是打包的意思,實際上就是綁定、生效 self.num2Input = Entry(self) self.num2Input.pack() self.alertButton = Button(self, text='兩數之和', command=self.calc) self.alertButton.pack() #能夠看到,每次咱們建立一個Lable或者Input或者alertButton時,須要pack self.quitButton = Button(self, text='退出', command=self.quit) self.quitButton.pack() def calc(self): num1 = int(self.num1Input.get()) or 0 num2 = int(self.num2Input.get()) or 0 messagebox.showinfo('Message', '和爲: %s' % (num1 + num2)) app = Application() app.master.title('求兩數之和') app.mainloop()
結果以下:python
電子郵件的歷史比web要久,直到如今,email也是很是經常使用的。幾乎全部的編程語言都支持發送和接受電子郵件。mysql
那麼電子郵件如何發送呢?好比個人郵件時me@163.com,對方的時friend@sina.com,咱們用Outlook或者Foxmail之類的軟件寫好郵件,填上對方的Email地址,點「發送」,電子郵件就發送出去了。這些電子郵件是MUA:Mail User Agent --- 郵件用戶代理。Email從MUA發送出去,不是直接到達對方電腦,而是發到MTA:Mail Transfer Agent --- 郵件傳輸代理,就是那些Email服務提供商,好比網易、新浪等。 因爲咱們本身的電子郵件時163.com,因此Email首先被投遞到網易提供的MTA,再由網易的MTA發送到對方的服務商,就是新浪的MTA,這個過程可能還會通過別的MTA,可是咱們不關心具體路線,只關心速度。Email到達新浪的MTA後,會把郵件投遞到目的地MDA : Mail Delivery Agent --- 郵件投遞代理。 email到達MDA後,就會存儲在新浪服務器的數據庫裏,這個長期保存郵件的地方是電子郵件。 同普通郵件相似,Emial不會直接到達對方的電腦,由於對方電腦不必定開機,開機也不必定聯網。對方要收到郵件,必須經過MUA從MDA上把郵件取到本身的電腦上。ios
所以:web
發件人 -> MUA -> MTA -> MTA -> 若干個MTA -> MDA <- MUA <- 收件人
有了上述基本概念,因此編寫程序來收發郵件,就是:redis
編寫MUA把郵件發到MTA;
編寫MUA從MDA上收郵件
發郵件時,MUA和MTA使用的協議就是SMTP:Simple Mail Transfer Protocol,後面的MTA到另外一個MTA也是用SMTP協議。
收郵件時,MUA和MDA使用的協議有兩種:POP:Post Office Protocol,目前版本是3,俗稱POP3;IMAP:Internet Message Access Protocol,目前版本是4,優勢是不但能取郵件,還能夠直接操做MDA上存儲的郵件,好比從收件箱移到垃圾箱,等等。
在使用Python收發郵件前,請先準備好至少兩個電子郵件,如xxx@163.com
,xxx@sina.com
,xxx@qq.com
等,注意兩個郵箱不要用同一家郵件服務商。
SMTP是發送郵件的協議,而python內置對SMTP協議的支持,能夠發送純文本郵件、html郵件、附件郵件。
from email.mime.text import MIMEText msg = MIMEText('hello, I am wayne zhu', 'plain', 'utf-8') #郵件正文、MIME的subtype、utf-8 from_addr = input('From:') password = input('Password:') #輸入收件人地址 to_addr = input('To: ') #輸入SMTP服務器地址: smtp_server = input('SMTP server: ') import smtplib # server -> login -> send -> quit server = smtplib.SMTP(smtp_server, 25) #SMTP協議默認端口是25 server.set_debuglevel(1) #能夠打印出和SMTP服務器交互的全部信息 server.login(from_addr, password) #登陸SMTP服務器 server.sendmail(from_addr, [to_addr], msg.as_string()) #發送郵件 server.quit()
總之,使用python是可使用內置的SMTP協議發送郵件的。
POP3收取郵件也是很是容易的,即編寫一個MUA做爲客戶端,而後從MDA把郵件獲取到用戶的電腦或者手機上。 收取郵件最經常使用的就是POP協議,目前版本爲3,即POP3。
python內置了一個poplib模塊,實現了pop3協議,能夠直接用來收郵件。而收郵件能夠分爲兩步:
第一: 用poplib把郵件的原始文本下載到本地。
第二: 用email解析原始文本,還原爲郵件對象。
import poplib # 輸入郵件地址, 口令和POP3服務器地址: email = input('Email: ') password = input('Password: ') pop3_server = input('POP3 server: ') # 鏈接到POP3服務器: server = poplib.POP3(pop3_server) # 能夠打開或關閉調試信息: server.set_debuglevel(1) # 可選:打印POP3服務器的歡迎文字: print(server.getwelcome().decode('utf-8')) # 身份認證: server.user(email) server.pass_(password) # stat()返回郵件數量和佔用空間: print('Messages: %s. Size: %s' % server.stat()) # list()返回全部郵件的編號: resp, mails, octets = server.list() # 能夠查看返回的列表相似[b'1 82923', b'2 2184', ...] print(mails) # 獲取最新一封郵件, 注意索引號從1開始: index = len(mails) resp, lines, octets = server.retr(index) # lines存儲了郵件的原始文本的每一行, # 能夠得到整個郵件的原始文本: msg_content = b'\r\n'.join(lines).decode('utf-8') # 稍後解析出郵件: msg = Parser().parsestr(msg_content) # 能夠根據郵件索引號直接從服務器刪除郵件: # server.dele(index) # 關閉鏈接: server.quit()
下面就解析郵件就能夠了。再也不贅述。
數據庫用於存儲數據,這對於任何應用都是不可或缺的。
而數據庫通常能夠分爲關係型數據庫(SQL)和非關係型數據庫(NoSQL)。注意,自己來講SQL是結構化查詢語言的意思,SQL是一種語言,可是不少狀況下說SQL是關係型數據庫,也能夠說是一種語言,根據語境理解。
關係型數據庫包括:
非關係型數據庫,其實並非咱們字面上理解的非關係型數據庫,實際上NoSQL的全稱不是not SQL,而是 not only sql,即不只僅是關係型數據庫, 因此nosql的功能通常更爲強大一些,效率更高一些。
SQLite是一種嵌入式數據庫,適合桌面和移動應用,他的數據庫是一個文件,自己用C寫的,體積很小,且python中就內置了SQLite,因此,在python中使用SQLite,不須要安裝任何東西,直接使用。
衆所周知,微信在後臺服務器中不保存聊天記錄,微信在移動客戶端全部的聊天記錄都會存在嵌入式數據庫SQLite中,一旦這個數據庫損壞,將會丟失用戶多年的聊天記錄。而騰訊監控到現網的損壞率是0.02%,也就是沒1w個數據庫就會有2個遇到數據庫損壞。考慮到這麼龐大的用戶基數,若是是10億,那麼就有20萬個用戶數據庫損壞,且以前微信的官方修復方法,修復成功率只有30%,損壞率高,修復率低。 而損壞的主要緣由就是空間不足、設備斷電、文件sync失敗,須要一一優化。
優化空間佔用 。 微信朋友圈會自動刪除7天前緩存的圖片。可是總的來講對文件空間的使用缺少一個全局把握。因此,業務文件須要先申請後使用、每一個業務文件都要申明有效期,過時文件就會被自動清理。而對於微信以外的佔用空間,好比相冊、視頻、其餘app的空間佔用,微信自己是作不了什麼工做的,只能提示用戶進行空間清理。
優化文件sync。第一:synchronous = Full, 設置SQLite的文件同步機制爲全同步,也要求每一個事物的寫操做是真的flush到文件裏去了。第二:fullfsync = 1,即微信團隊與蘋果工程師交流以後發現ios平臺下還有fullfsync這個選項,能夠嚴格保證吸入順序和提交順序一致,設備開發商爲了測評數據好看,每每會對提交的數據重排,再統一寫入,即寫入順序和app提交順序不一致。但在某些狀況下好比斷電會致使不一致。
優化效果。優化以後,微信團隊使得損壞率下降了一半多。
可是微信團隊在後臺是否保留了聊天記錄我仍是存疑的,可是即便保留,應該也是按期會清理的,好比只保留最近一到兩年的。
注意:表是數據庫中存放關係數據的集合,一個數據庫中包含多個表,表和表之間使用外鍵連接,要操做關係數據庫,首先要鏈接到數據庫,一個數據庫連接成爲Connection; 連接到數據庫以後,須要打開遊標,稱之爲Cursor,經過Cursor執行SQL語句,而後,得到執行結果 。
# 導入SQLite驅動: >>> import sqlite3 # 鏈接到SQLite數據庫 # 數據庫文件是test.db # 若是文件不存在,會自動在當前目錄建立: >>> conn = sqlite3.connect('test.db') # 建立一個Cursor: >>> cursor = conn.cursor() # 執行一條SQL語句,建立user表: >>> cursor.execute('create table user (id varchar(20) primary key, name varchar(20))') <sqlite3.Cursor object at 0x10f8aa260> # 繼續執行一條SQL語句,插入一條記錄: >>> cursor.execute('insert into user (id, name) values (\'1\', \'Michael\')') <sqlite3.Cursor object at 0x10f8aa260> # 經過rowcount得到插入的行數: >>> cursor.rowcount 1 # 關閉Cursor: >>> cursor.close() # 提交事務: >>> conn.commit() # 關閉Connection: >>> conn.close()
接下來,咱們能夠試着查詢記錄:
>>> conn = sqlite3.connect('test.db') >>> cursor = conn.cursor() # 執行查詢語句: >>> cursor.execute('select * from user where id=?', ('1',)) <sqlite3.Cursor object at 0x10f8aa340> # 得到查詢結果集: >>> values = cursor.fetchall() >>> values [('1', 'Michael')] >>> cursor.close() >>> conn.close()
以前使用的SQLite的特色是輕量級、可嵌入,可是不能承受高併發訪問,適合桌面和移動應用。 而MySQL是爲服務器端設計的數據庫,能承受高併發訪問,同時佔用的內存也遠遠大於SQLite。
嵌入式咱們聽得不少,那嵌入式到底是什麼呢?爲何說SQLite是嵌入式的呢?
由於嵌入式值得是把軟件(代碼)直接燒錄在硬件裏(不須要從外部獲取),而不是安裝在外部存儲介質上。好比SQLite就是把數據庫文件直接存在本地,而不是從服務器端獲取,因此是嵌入式的。
因爲MySQL服務器以獨立的進程運行,並經過網絡對外服務,因此,須要支持python的MySQL驅動來連接到MySQL服務器。 MySQL官方提供了mysql-connector-python驅動,可是安裝的時候須要給pip命令加上參數 --allow-external:
pip install mysql-connector-python --allow-external mysql-connector-python
下面,咱們就能夠鏈接到MySQL服務器的test數據庫:
# 導入MySQL驅動: >>> import mysql.connector # 注意把password設爲你的root口令: >>> conn = mysql.connector.connect(user='root', password='password', database='test') >>> cursor = conn.cursor() # 建立user表: >>> cursor.execute('create table user (id varchar(20) primary key, name varchar(20))') # 插入一行記錄,注意MySQL的佔位符是%s: >>> cursor.execute('insert into user (id, name) values (%s, %s)', ['1', 'Michael']) >>> cursor.rowcount 1 # 提交事務: >>> conn.commit() >>> cursor.close() # 運行查詢: >>> cursor = conn.cursor() >>> cursor.execute('select * from user where id = %s', ('1',)) >>> values = cursor.fetchall() >>> values [('1', 'Michael')] # 關閉Cursor和Connection: >>> cursor.close() True >>> conn.close()
能夠看到,對於MySQL的連接仍是和SQLite是相似的,引入mysql.connector以後,就可使用用戶名、密碼鏈接到到指定的數據庫,而後得到cursor,最後就可使用cursor.execute來執行相關的SQL語句了。
注意:在執行insert操做以後要使用commit()提交事務。
數據庫是一個二維表,以下所示:
[ ('1', 'Michael'), ('2', 'Bob'), ('3', 'Adam') ]
咱們還能夠經過下面的class實例很容易的看出結構:
class User(object): def __init__(self, id, name): self.id = id self.name = name [ User('1', 'Michael'), User('2', 'Bob'), User('3', 'Adam') ]
這就是ORM技術了,即Object-Relational Mapping,對象關係映射,能夠把關係數據庫的表結構映射到對象上,即ORM將數據庫中的表與面嚮對象語言中的類創建了一種對應關係。
SQLAlchemy是python社區最爲知名的ORM工具之一,爲高效和高性能的數據庫訪問設計,實現了完整的企業級持久模型。
首先,咱們經過pip安裝:
pip install sqlalchemy
而後,利用上次咱們在MySQL上的test數據庫中建立的user表,用SQLAlchemy來試一試:
第一步,導入SQLAlchemy,並初始化DBSession:
# 導入: from sqlalchemy import Column, String, create_engine from sqlalchemy.orm import sessionmaker from sqlalchemy.ext.declarative import declarative_base # 建立對象的基類: Base = declarative_base() # 定義User對象: class User(Base): # 表的名字: __tablename__ = 'user' # 表的結構: id = Column(String(20), primary_key=True) name = Column(String(20)) # 初始化數據庫鏈接: engine = create_engine('mysql+mysqlconnector://root:password@localhost:3306/test') # 建立DBSession類型: DBSession = sessionmaker(bind=engine)
以上代碼完成SQLAlchemy的初始化和具體每一個表的class定義。若是有多個表,就繼續定義其餘class,例如School:
class School(Base): __tablename__ = 'school' id = ... name = ...
。。。。
ORM框架的做用就是把數據庫表的一行記錄與一個對象互相作自動轉換。 而正確使用ORM的前提是瞭解關係數據庫的原理。
最先的軟件是運行在大型機上的,軟件使用者經過啞終端登陸到大型機上去運行軟件,後來PC機的興起,軟件開始運行在桌面上,而數據庫這樣的軟件運行在服務器上,這種Clinet/Server模式成爲C/S架構。
隨着互聯網的興起,人們發現,CS架構不適合Web,最大的緣由是桌面app的升級很是麻煩,須要用戶從新下載,而瀏覽器上的web應用程序只要服務器端改變就改變了,升級很是迅速,所以,Browser/Server模式開始流行,簡稱爲B/S架構。
python的誕生歷史比web還要早,且因爲python是一種解釋型的腳本緣由,開發效率高,因此很是適合作web開發。
由於BS架構的實質就是瀏覽器發送一個http請求,而後用一個現成的HTTP服務器來接受用戶請求,從文件中讀取html並返回,Apache、Nginx、Lighttpd等這些常見的靜態服務器就是幹這件事的。
而咱們使用python怎麼辦呢?難道要本身封裝http請求、TCP連接等這些基礎的任務嗎? 不是的! 爲了咱們專心使用python編寫web業務,python內置了接口: WSGI --- web server gateway interface。經過這個接口,咱們就能夠很容易的使用python作後臺編寫web業務了。
定義以下所示:
def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) return [b'<h1>Hello, web!</h1>']
如上所示的application就是一個web處理函數,它接受兩個參數:
固然,一個web處理函數,固然要先接受http請求的相關信息,而後再進行http響應,有了請求和響應,就能夠構成一個服務器的web處理函數了。
而application中start_response函數的調用也接受了兩個參數:
最後,return了一個字符串,這個就作爲了http響應的body發送給了瀏覽器。
如上所示,application函數接受了http請求做爲參數,解析以後,能夠發出http響應,即包含響應頭和body。這樣,使用python的wsgi就能夠輕易的完成http請求和響應了,而不須要接觸底層的代碼。
可是,這個application()函數如何調用呢? 還要傳入environ參數和start_response函數,這個咱們怎麼寫呢?通常應該是服務器調用的啊。 好消息是python內置了一個wsgi服務器,這個模塊叫作wsgiref,它是純python編寫的wsgi服務器的參考實現,所謂‘參考實現’是指徹底符合wsgi標準,可是不考慮任何運行效率,進攻開發和測試使用。
運行wsgi服務
ok,咱們先編寫foo.py,以下:
def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) return [b'<h1>Hello, web!</h1>']
接下來就是引入wsgi服務器模塊,而後着手搭建一個服務器並調用上面的處理函數,以下爲server.py:
# server.py # 從wsgiref模塊導入 from wsgiref.simple_server import make_server #導入以前編寫的application函數 from foo import application #建立http服務器,ip地址爲空(經過localhost訪問),端口是8000,處理函數是application http = make_server('', 8000, application) print('Serving HTTP on port 8000...') #開始監聽HTTP請求 http.serve_forever()
如上,運行這個server.py文件以後,咱們就能夠在瀏覽器localhost:8000打開了,以下:
注意:但咱們在命令行中輸入 python server.py時,只是打開了服務器,由於沒有請求,因此application函數沒有調用,而一旦在瀏覽器中輸入localhost:8000,這個application函數纔會處理http請求,而後作出響應。
固然,若是咱們還能夠從environ中讀取到更多的信息,好比,咱們將foo.py中的application函數中添加代碼以下:
for i,j in environ.items(): print(i,j)
這樣,在啓動服務器而後進行請求以後,就會打印出environ這個請求dict的具體條目,以下我選取了一些經常使用的:
SERVER_PORT 8000 SERVER_PROTOCOL HTTP/1.1 REQUEST_METHOD GET PATH_INFO / QUERY_STRING REMOTE_ADDR 127.0.0.1 CONTENT_TYPE text/plain HTTP_HOST localhost:8000 HTTP_CONNECTION keep-alive HTTP_CACHE_CONTROL max-age=0 HTTP_USER_AGENT Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36 HTTP_ACCEPT text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 HTTP_ACCEPT_ENCODING gzip, deflate, br HTTP_ACCEPT_LANGUAGE zh-CN,zh;q=0.9,en;q=0.8 HTTP_COOKIE _ga=GA1.1.239236614.1512745292 wsgi.run_once False
如上,包括服務器端口號8000、http協議使用的版本1.1、請求方法get、PATH_INFO /(即loacalhost以後的path)、QUERY_STRING查詢字符串、遠程地址、CONTENT_TYPE、HTTP_HOST等等都是請求中很是重要的參數。
因此,對於application,咱們也能夠寫的更爲複雜一些:
def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) body = '<h1>Hello, %s</h1>' % (environ['PATH_INFO'][1:] or 'web') body = body + '<h2> request method %s </h2>' % (environ['REQUEST_METHOD'] or 'get') body = body + '<h2> content-type %s' % (environ['CONTENT_TYPE']) return [body.encode('utf-8')]
如上所示,字符串可使用 + 來進行拼接,最後的結果以下:
使用web框架比使用wsgi更抽象,寫起來邏輯更清楚,效率更高,而Flask框架就是這樣的框架,它比較簡單、輕量,咱們首先安裝Flask:
pip install flask
而後,寫一個app.py,處理三個URL,分別是:
代碼以下所示:
from flask import Flask from flask import request app = Flask(__name__) @app.route('/', methods=['GET', 'POST']) def home(): return '<h1>Home</h1>' @app.route('/signin', methods=['GET']) def signin_form(): return '''<form action="/signin" method="post"> <p><input name="username"></p> <p><input name="password" type="password"></p> <p><button type="submit">Sign In</button></p> </form>''' @app.route('/signin', methods=['POST']) def signin(): if request.form['username'] == 'admin' and request.form['password'] == 'password': return '<h3>Hello, admin!</h3>' return '<h3>Bad username or password.</h3>' if __name__ == '__main__': app.run()
運行以下:
C:\Users\Administrator\Desktop>python app.py * Restarting with stat * Debugger is active! * Debugger PIN: 211-799-158 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
而後咱們在瀏覽器打開http://127.0.0.1:5000/便可,能夠看到,使用flask框架以後,咱們這裏使用了裝飾器,即高級函數,這樣,咱們使用route接受兩個參數,第一個是PATH,第二個指定方法便可,接下來定義一個返回值。
而且,對於wsgi咱們在修改代碼以後須要重啓服務器,而使用了flask框架以後,咱們在修改代碼以後服務器會自動重啓更新。且flask制定了域名和端口,而不須要咱們自行設定。
另外,flask將response的內容又進行了封裝,而咱們只須要寫一些核心的函數便可。
另外,request即用戶的請求對象,咱們能夠打印其中的一些結果:
@app.route('/', methods=['GET', 'POST']) def home(): print(request.method) print(request.host) print(request.headers) print(request.data) print(request.values) print(request.url) return '<h1>waynezhu</h1>'
在後臺能夠看到數據以下:
GET 127.0.0.1:5000 Host: 127.0.0.1:5000 Connection: keep-alive Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 b'' CombinedMultiDict([ImmutableMultiDict([]), ImmutableMultiDict([])]) http://127.0.0.1:5000/
注意:python是敏捷開發很好的語言,而java的開發就作不到python如此敏捷,由於java的開發量會比較大,難以作出迅速的改變,而python開發迅速,你能夠在一兩個小時就作出一個功能,這樣對於創業型公司來講,需求更改頻繁,使用python也能夠很好的應付過來。
固然,Flask是很是流行,對初學者也是很友好的,可是還有其餘的框架使用的也很是普遍,以下:
HTML在大型網站中仍是很是複雜的,若是都像以前的flask程序中那樣返回html是不可取的,何況通常咱們還須要大量的css和js,顯然咱們須要換一種方式來返回HTML文件。因而,模板就出現了。
使用模板,咱們須要預先準備一個HTML文檔,這個HTML文檔不是普通HTML,而是嵌入了一些變量和命令,而後,根據咱們傳入的數據,替換後,就獲得了最終的HTML,發送給用戶:
這就是MVC的設計模式了,M就是model,即數據,好比這裏替換的name就是數據; V是view,而後這個html模板就是view了,即展現在用戶面前的;而C是controller,是控制器,即檢查用戶名是否存在、取出用戶信息等。
那麼使用了模板以後,咱們就能夠將以前的app.py加以修改了,下面使用到的render_template函數就是渲染模板,即將模板渲染爲html文件並將其中的變量等加以替換。
from flask import Flask,request,render_template app = Flask(__name__) @app.route('/', methods=['GET', 'POST']) def home(): return render_template('home.html') @app.route('/signin', methods=['GET']) def signin_form(): return render_template('form.html') @app.route('/signin', methods=['POST']) def signin(): username = request.form['username'] password = request.form['password'] if username == 'admin' and password == 'password': return render_template('signin-ok.html', username=username) return render_template('form.html', message='Bad username or password', username=username) if __name__ == '__main__': app.run()
如上所示,咱們就完成了改寫,下面咱們就要開始寫模板了,在python中內置了jinja2模板,咱們直接pip install jinja2便可,注意安裝以後是不須要引入的,而後編寫下面的模板。
home.html以下
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Home</title> </head> <body> <h1 style="font-style: italic">Home</h1> </body> </html>
form.html以下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Please Sign in</title> </head> <body> {% if message %} <p style="color: red">{{ message }}</p> {% endif %} <form action="/signin" method="post"> <legend>Please sign in:</legend> <p><input name="username" placeholder="username" value="{{ username }}" type="text"></p> <p><input name="password" placeholder="password" type="password"></p> <p><button type="submit">Sign In</button></p> </form> </body> </html>
能夠看到jinja2的語法是使用{{}}來包含變量,而且其中的python代碼使用{% %}來包含。
signin-ok.html以下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>success</title> </head> <body> <p>Welcome, {{ username }}</p> </body> </html>
這樣,咱們就能夠python app.py運行並訪問了!
如上,若是登陸失敗,會提示登陸失敗,而且將以前的用戶名保留,這是很是符合咱們使用網站登陸的習慣的。
這個仍是很好理解的,js、node中有不少異步io的例子。那麼異步IO出現的緣由是什麼呢?
程序執行的過程當中,每每會遇到IO的狀況,好比網絡請求、硬盤文件讀取,而這些都是很是耗時的,在處理網絡、文件讀取這些IO操做的時候,CPU只能處於空閒狀態,這樣後面的代碼也是沒法執行的,只有等到IO結束,才能執行後面的代碼。 爲了解決這個問題,就有兩種解決方式了:
協程的概念早就出來了,英文名是Coroutine,只是最近幾年纔在Lua中獲得普遍應用而被提起。
而在JavaScript中咱們也是有協程的概念的,個人文章就有介紹。好比一個A函數和一個B函數,執行A函數到某個語句時,可能會忽然中止,轉向去執行B,而後B又執行了一段時間,又轉向去執行A,可是A和B函數是獨立的,並非A中調用了B或者B中調用A,這就是協程,有有些像多線程,但協程不是多線程,由於協程的特色就是隻有一個線程執行。
而協程優勢:執行效率高於多線程,由於沒有線程切換的開銷,且與之比較的線程數量越多,協程的性能優點越明顯。另外,協程是一個線程執行,因此不須要多線程的鎖機制,不存在變量衝突的問題,只判斷狀態就好,因此執行效率比較高。
和JavaScript相同,Python中協程的實現也是經過generator實現的,在generator中,咱們不但能夠經過for循環來迭代,還能夠不斷調用next()函數獲取由yield語句返回的下一個值。
def consumer(): r = '' while True: n = yield r if not n: return print('[CONSUMER] Consuming %s...' % n) r = '200 OK' def produce(c): c.send(None) n = 0 while n < 5: n = n + 1 print('[PRODUCER] Producing %s...' % n) r = c.send(n) print('[PRODUCER] Consumer return: %s' % r) c.close() c = consumer() produce(c)
如上所示,就是一個協程的例子,執行結果以下:
[PRODUCER] Producing 1... [CONSUMER] Consuming 1... [PRODUCER] Consumer return: 200 OK [PRODUCER] Producing 2... [CONSUMER] Consuming 2... [PRODUCER] Consumer return: 200 OK [PRODUCER] Producing 3... [CONSUMER] Consuming 3... [PRODUCER] Consumer return: 200 OK [PRODUCER] Producing 4... [CONSUMER] Consuming 4... [PRODUCER] Consumer return: 200 OK [PRODUCER] Producing 5... [CONSUMER] Consuming 5... [PRODUCER] Consumer return: 200 OK
這裏的consumer函數是一個generator,把一個consumer傳入produce以後:
首先調用c.send(None)
啓動生成器;注意: send函數和next函數的做用是相似的。
而後,一旦生產了東西,經過c.send(n)
切換到consumer
執行;
consumer
經過yield
拿到消息,處理,又經過yield
把結果傳回;
produce
拿到consumer
處理的結果,繼續生產下一條消息;
produce
決定不生產了,經過c.close()
關閉consumer
,整個過程結束。
能夠看到,通常,咱們使用generator就能夠輕易的實現一個協程了,而且,協程的執行過程也是很是容易理解的,就是在generator中的yield中斷,到另一個函數執行,另一個函數執行到send的時候,就會再回到generator執行了,如此循環往復便可。
整個流程無鎖,由一個線程執行,produce和consumer協做完成任務,因此稱爲「協程」,而非線程的搶佔式多任務。
asyncio是Python 3.4版本引入的,直接內置了對異步IO的支持,他的編程模型是一個消息循環,咱們從asyncio模塊中直接獲取一個EventLoop的引用,而後把須要執行的協程扔到EventLoop中執行,就實現了異步IO。
import asyncio @asyncio.coroutine def hello(): print("Hello world!") # 異步調用asyncio.sleep(1): r = yield from asyncio.sleep(1) print("Hello again!") # 獲取EventLoop: loop = asyncio.get_event_loop() # 執行coroutine loop.run_until_complete(hello()) loop.close()
@asyncio.coroutine把一個generator標記爲coroutine類型,而後,咱們就把這個coroutine扔到EventLoop中執行了,首先hello()打印出了Hello world!,而後yield from拿到返回值,而後接着執行下一句,asyncio.sleep(1)能夠當作是一個耗時1秒的IO操做,這個過程當中,主線程並無等待,而是去執行EventLoop中其餘能夠執行的coroutine了,所以能夠實現併發執行。
下面,咱們能夠用Task封裝兩個coroutine試一試:
import threading import asyncio @asyncio.coroutine def hello(num): print(num, 'Hello world! (%s)' % threading.currentThread()) #打印當前線程名稱 yield from asyncio.sleep(1) print(num, 'Hello again! (%s)' % threading.currentThread()) loop = asyncio.get_event_loop() tasks = [hello(1), hello(2)] loop.run_until_complete(asyncio.wait(tasks)) loop.close() #注意:最後必定要關閉這個EventLoop
如上所示,咱們引入了threading模塊用來打印當前線程,用asyncio來方便實現協程並交由EventLoop,接着咱們建立了一個tasks,最後開始執行,執行完畢,咱們就須要關閉這個EventLoop,結果以下:
2 Hello world! (<_MainThread(MainThread, started 6536)>) 1 Hello world! (<_MainThread(MainThread, started 6536)>) 2 Hello again! (<_MainThread(MainThread, started 6536)>) 1 Hello again! (<_MainThread(MainThread, started 6536)>)
能夠看到,咱們傳入的時1和2,可是在執行的時候卻先執行的2後執行的1,而後2執行到yield的時候有一個IO操做,因此這時cpu並無停下來,而是在EventLoop中找到了另一個去執行,等到IO返回的時候,又回到了2去執行,以後又去執行1。 而且因爲這是協程來實現的併發執行,因此能夠看到兩個 coroutine 都是由同一個進程併發執行的。因此,即便是把asyncio.sleep()替換成真正的IO操做,則多個coroutine就能夠由一個線程併發執行了。
因此,咱們能夠看到asyncio提供了完善的異步IO支持; 異步操做須要在coroutine中經過yield from完成(即這個yield from 後面應當是一個IO操做,這個操做是費時的,且無需CPU),那麼這時CPU就會去tasks中的其餘任務使用,而使得CPU的利用更充分。固然,多個coroutine能夠封裝成一組Tasks而後併發執行。
用asyncio提供的@asyncio.coroutine能夠把一個generator標記爲coroutine(協程)類型,而後在coroutine內部用yield from低啊用另一個coroutine實現異步操做。
而爲了更好的標識異步IO,從python 3.5開始引入了新的語法async和await,可讓coroutine的代碼更簡潔易讀。 注意: async和await是針對oroutine的新語法,本質沒有變,要使用這個語法,須要作以下替換:
而剩下的徹底不變,咱們對上面的例子作出修改,以下:
import threading import asyncio
async def hello(num): print(num, 'Hello world! (%s)' % threading.currentThread()) #打印當前線程名稱 await asyncio.sleep(1) print(num, 'Hello again! (%s)' % threading.currentThread()) loop = asyncio.get_event_loop() tasks = [hello(1), hello(2)] loop.run_until_complete(asyncio.wait(tasks)) loop.close() #注意:最後必定要關閉這個EventLoop
如上所示,咱們能夠發現,使用了async/await以後代碼更加簡潔,最終的結果也是一致的。因此說,這裏的async/await函數也是能夠理解爲協程了。另外,這個await的返回值是存在的,左邊也能夠有一個變量接受返回值,固然若是後面的式子沒有指定返回值,那麼返回值就是None了。
asyncio能夠實現單線程併發IO操做,若是僅僅用在客戶端,發揮的威力不大,但把asyncio用在服務器端,例如web服務器,因爲HTTP連接就是IO操做,所以能夠用單線程+coroutine實現多用戶的高併發支持。
asyncio實現了TCP、UDP、SSL等協議,aiohttp則是基於asyncio實現的http框架, aio 解釋爲 asyncio 的簡稱,http表示這是一種http框架。
以下,咱們首先安裝aiohttp:
pip install aiohttp
而後編寫一個HTTP服務器,分別處理如下URL:
/
- 首頁返回b'<h1>Index</h1>'
;
/hello/{name}
- 根據URL參數返回文本hello, %s!
。
代碼以下所示:
import asyncio from aiohttp import web async def index(request): await asyncio.sleep(0.5) return web.Response(body=b'<h1>Index</h1>') async def hello(request): await asyncio.sleep(0.5) text = '<h1>hello, %s!</h1>' % request.match_info['name'] return web.Response(body=text.encode('utf-8')) async def init(loop): app = web.Application(loop=loop) app.router.add_route('GET', '/', index) app.router.add_route('GET', '/hello/{name}', hello) srv = await loop.create_server(app.make_handler(), '127.0.0.1', 8000) print('Server started at http://127.0.0.1:8000...') return srv loop = asyncio.get_event_loop() loop.run_until_complete(init(loop)) loop.run_forever()
注意aiohttp的初始化函數init()也是一個coroutine,loop.create_server()則利用asyncio建立TCP服務。
能夠看到,這就是web後臺處理網絡請求的代碼了,對於IO來講,接收到一個請求,咱們每每須要進入數據庫,讀取某些文件,而後再返回給前臺,可是打開數據庫,讀取文件的這個過程是比較緩慢的,因此,這裏使用了異步IO以後,咱們就能夠在打開數據庫、讀取文件的過程當中使得這個線程處理其餘程序,即把此時空閒的cpu交給其餘程序使用,而後等到IO操做結束以後,咱們就可使得CPU繼續在原來的程序工做,這樣的異步IO操做就使得CPU的利用率很高,而且不會出現阻塞的狀況了,一樣的Nodejs的優勢也是異步IO,這樣也就解決了高併發的問題。