Flask簡介與URL和視圖介紹
一、虛擬環境介紹
1.虛擬環境與全局環境
有時候安裝了一個Python庫,可能在IDE如PyCharm中不能使用,這是因為:
通過pip安裝的庫默認一般在全局環境中,而PyCharm一般會默認創建虛擬環境,所以兩者的環境不一致,導致安裝的包不能正常導入使用,解決辦法有2種:
- 在PyCharm虛擬環境中安裝庫,使庫位於虛擬環境中
- 將PyCharm的環境設置為全局環境,即我們通常使用的Python,設置為Python的安裝目錄即可
設置示意如下:
2.為什么需要虛擬環境
一般情況下,Python第三方庫安裝是直接通過pip install xxx
的方式進行安裝的,這樣安裝會將庫安裝到系統級的Python環境中。
但是有時可能會面臨這樣的問題:如果現在用Django 1.10.x寫了個網站,但是同時有一個Django 0.9開發的項目需要維護,並i企鵝可能Django 1.10不再兼容Django 0.9的一些語法了,這就需要同時擁有Django 1.10和Django 0.9兩套環境,這時候我們就可以通過虛擬環境來解決這個問題。
3.虛擬環境的安裝和簡單操作
虛擬環境管理有很多工具,這里我選擇pipenv。
pipenv的安裝
命令:
- Windows下
pip install pipenv
- Mac下
brew install pipenv
- Linux下
pip install pipenv
創建虛擬環境
安裝之后即可創建虛擬環境。
創建虛擬環境使用命令pipenv shell
,如下:
出現圖中所標提示Flask_Framework-rL0Lvhvz
及說明安裝成功,此時再運行pip list
可以看到虛擬環境中默認安裝的庫:
Package Version
---------- -------
pip 20.0.2
setuptools 46.1.3
wheel 0.34.2
不能同時使用全局環境和虛擬環境的庫,只能選擇使用其中一個。
虛擬環境默認會安裝到系統盤(C盤)下的當前用戶目錄下的 .virtualenvs 目錄下,如果想指定安裝到其他目錄,可以設置系統環境變量,示意如下:
此時再安裝虛擬環境,即會安裝到指定的目錄下。
虛擬環境安裝好之后,需要在PyCharm中設置虛擬環境為當前創建的虛擬環境,即定位選擇虛擬環境下的python.exe文件,與前面方法相同。
需要在虛擬環境中通過命令pip install flask
安裝Flask,再查看安裝的庫,結果如下:
Package Version
------------ -------
click 7.1.1
Flask 1.1.2
itsdangerous 1.1.0
Jinja2 2.11.1
MarkupSafe 1.1.1
pip 20.0.2
setuptools 46.1.3
Werkzeug 1.0.1
wheel 0.34.2
顯然,在安裝flask時,安裝了存在依賴關系的其他庫。
如果電腦中同時擁有Python3和Python2,可以指定版本:
pipenv --three # 泛指Python3的版本
pipenv --two # 泛指Python2的版本
pipenv --python 3.7 # 指定Python具體版本
虛擬環境管理
pipenv shell # 如果虛擬環境已存在則進入虛擬環境,否則創建並進入虛擬環境
exit # 退出虛擬環境
pipenv --rm # 刪除整個環境 不會刪除pipfile
pipfile和pipfile.lock
在創建虛擬環境后,虛擬環境目錄下會生成pipfile文件,內容如下:
[[source]]
name = “pypi”
url = “https://pypi.org/simple”
verify_ssl = true[dev-packages]
[packages]
[requires] python_version = “3.7”
參數說明:
- url
可以指定國內pip源,否則下載庫可能會很慢 - dev-packages
開發環境 - packages
生產環境 - django = “*”
*表示最新版本 - requires
Python版本
pipfile.lock詳細記錄環境依賴,並且使用了Hash算法以保證完整的對應關系。
如果需要將安裝的庫記錄到Pipfile中,可以使用pip install --dev 庫名
將庫安裝到開發環境。
在虛擬環境中運行項目示意run參數:
pipenv run python manage.py runserver
pipenv有一個缺點:
lock不穩定而且時間非常長,所以安裝包的時候記得加上--skip-lock
,如下:
pipenv install django --skip-lock
最后開發完成要提交到倉庫的時候再執行pipenv lock
命令。
二、Flask介紹
1.Flask簡介
flask是一款非常流行的Python Web框架,誕生於2010年,作者是Armin Ronacher,這個項目最初只是作者在愚人節的一個玩笑,后來由於非常受歡迎,逐漸成為一個正式的項目。
flask自2010年發布第一個版本以來,大受歡迎,深得開發者的喜愛,並且在多個公司已經得到了應用,flask能如此流行的原因,可以分為以下幾點:
- 微框架、簡潔,只做它需要做的,靈活度非常高,給開發者提供了很大的擴展性。
Flask不會幫開發者做太多的決策,一切都可以按照自己的意願進行更改。- 使用Flask開發數據庫的時候,具體是使用SQLAlchemy還是MongoEngine,選擇權完全掌握在你自己的手中。區別於Django,Django內置了非常完善和豐富的功能,並且如果你想替換成你自己想要的,要么不支持,要么非常麻煩。
- 把默認的Jinija2模板引擎替換成其他模板引擎都是非常容易的。
- Flask和相應的插件寫得很好。
- 開發效率非常高,比如使用SQLAlchemy的ORM操作數據庫可以節省開發者書寫大量sql的時間。
2.第一個Flask程序
from flask import Flask
# 傳入__name__初始化一個Flask實例
app = Flask(__name__)
# 裝飾器,將當前路由映射到指定函數
@app.route('/')
def hello_world():
return 'hello world'
if __name__ == '__main__':
app.run()
打印:
* Serving Flask app "first_flask" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
此時已經創建服務,在瀏覽器中打開http://127.0.0.1:5000/即可看到:
並且在開啟的服務狀態欄下會看到請求的記錄,如:
127.0.0.1 - - [09/Apr/2020 07:54:10] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [09/Apr/2020 07:54:10] "GET /favicon.ico HTTP/1.1" 404 -
說明:
@app.route('/')
裝飾器映射URL和執行的函數。這個設置將URL映射到指定的函數上,例中指定當前路由為根目錄,如果為根目錄時也可以不寫 /,但是盡量寫上以示區別。app.run()
是讓flask項目運行起來,可以指定主機號和端口號。
默認的host是127.0.0.1,port為5000,host=0.0.0.0可以讓其他電腦也能訪問到該網站,port指定訪問的端口。
三、設置Debug模式
默認情況下flask不會開啟DEBUG模式,開啟DEBUG模式后,flask會在每次保存代碼的時候自動的重新載入代碼,並且如果代碼有錯誤,會在終端進行提示。
在hello_world()
函數中加入錯誤代碼進行測試:
from flask import Flask
app = Flask(__name__)
# 裝飾器,將當前路由映射到指定函數
@app.route('/')
def hello_world():
result = 1 / 0
return 'hello world'
if __name__ == '__main__':
app.run()
重新運行開啟服務后,會發現:
在日志中也會發現報錯:
[2020-04-09 08:07:17,881] ERROR in app: Exception on / [GET]
Traceback (most recent call last):
File "C:\Users\Lenovo\.virtualenvs\Flask_Framework-rL0Lvhvz\lib\site-packages\flask\app.py", line 2447, in wsgi_app
response = self.full_dispatch_request()
File "C:\Users\Lenovo\.virtualenvs\Flask_Framework-rL0Lvhvz\lib\site-packages\flask\app.py", line 1952, in full_dispatch_request
rv = self.handle_user_exception(e)
File "C:\Users\Lenovo\.virtualenvs\Flask_Framework-rL0Lvhvz\lib\site-packages\flask\app.py", line 1821, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "C:\Users\Lenovo\.virtualenvs\Flask_Framework-rL0Lvhvz\lib\site-packages\flask\_compat.py", line 39, in reraise
raise value
File "C:\Users\Lenovo\.virtualenvs\Flask_Framework-rL0Lvhvz\lib\site-packages\flask\app.py", line 1950, in full_dispatch_request
rv = self.dispatch_request()
File "C:\Users\Lenovo\.virtualenvs\Flask_Framework-rL0Lvhvz\lib\site-packages\flask\app.py", line 1936, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "xxx/first_flask.py", line 9, in hello_world
result = 1 / 0
ZeroDivisionError: division by zero
127.0.0.1 - - [09/Apr/2020 08:07:17] "GET / HTTP/1.1" 500 -
這與顯然很麻煩,每次修改之后必須重新運行,而且錯誤信息在日志中才能看到。
我們可以開啟Debug模式,這樣每次修改代碼后都會載入代碼重新運行,並且代碼有問題時會顯示錯誤信息。
開啟Debug模式有幾種方式:
- 在
run()
方法中設置debug參數為True
if __name__ == '__main__':
app.run(debug=True)
- 設置app對象實例的屬性為True
if __name__ == '__main__':
app.debug = True
app.run()
- 通過配置參數config設置
if __name__ == '__main__':
app.config.update(DEBUG=True)
app.run()
config是繼承自字典類型的,所以可以使用字典的update()
方法。
開啟Debug模式測試如下:
from flask import Flask
app = Flask(__name__)
# 裝飾器,將當前路由映射到指定函數
@app.route('/')
def hello_world():
result = 1 / 0
return 'hello world'
if __name__ == '__main__':
app.run(debug=True)
此時再看網頁:
並且控制台中也會提示已開啟Debug模式:
* Serving Flask app "first_flask" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Restarting with stat
* Debugger is active!
* Debugger PIN: 313-629-160
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
在開啟了DEBUG模式后,當程序有異常而進入錯誤堆棧模式,第一次點擊某個堆棧想查看變量值的時候,頁面會彈出一個對話框,提示輸入PIN值,比如在剛剛啟動的項目中的PIN值為313-629-160,輸入這個值后,Werkzeug會把這個PIN值作為cookie的一部分保存起來,並在8小時后過期,8小時內不需要再輸入PIN值。這樣做的目的是為了提高安全性,讓調試模式下的攻擊者更難攻擊到本站。
現在每次修改完代碼保存之后,都會自動加載代碼重啟服務,不需要再手動關閉服務再重啟了。
此時在報錯的網頁中可以Debug,需要使用控制台提供的PIN,操作示意如下:
Debug模式是在開發環境中開啟的,開發完成上線之后要關閉Debug模式,因為DEBUG模式會帶來非常大的安全隱患。
四、配置與配置文件
Flask項目的配置,都是通過app.config對象來進行配置的。
比如要配置一個項目處於DEBUG模式下,那么可以使用app.config['DEBUG] = True
來進行設置,那么Flask項目將以DEBUG模式運行。
在Flask項目中,有四種方式進行項目的配置。
1.直接硬編碼
app = Flask(__name__)
app.config['DEBUG'] = True
硬編碼的方式不靈活,不便於進行復用。
2.通過update()方法
因為app.config是flask.config.Config的實例,而Config類是繼承自dict,因此可以通過update()
方法進行配置。
app.config.update(
DEBUG=True,
SECRET_KEY='...'
)
3.通過from_object()方法
如果配置項特別多,可以把所有的配置項都放在一個模塊中,然后通過加載模塊的方式進行配置,假設有一個settings.py模塊,專門用來存儲配置項的,此你可以通過app.config.from_object()
方法進行加載,並且該方法既可以接收模塊的的字符串名稱,也可以模塊對象。
有兩種形式:
# 1. 通過模塊字符串
app.config.from_object('settings')
# 2. 通過模塊對象
import settings
app.config.from_object(settings)
添加配置文件后,將配置項都放入該文件中,其他文件直接引用該配置文件中的配置項,提高了代碼的復用性、降低了耦合度,同時,在配置文件中修改了配置項時,其他代碼中均不需要修改,從而提高了代碼的靈活性。
新建config.py文件,添加一些配置項如下:
# 設置Debug模式為True
DEBUG = True
# 指定HOST
HOST = '127.0.0.1'
在flask文件中導入:
from flask import Flask
import config
app = Flask(__name__)
# 裝飾器,將當前路由映射到指定函數
@app.route('/')
def hello_world():
result = 1 / 0
return 'hello world'
if __name__ == '__main__':
app.config.from_object(config)
app.run()
再運行,也能開啟Debug模式。
也可以通過字符串形式導入:
if __name__ == '__main__':
app.config.from_object('config')
app.run()
此時不需要再導入config模塊。
4.通過from_pyfile()方法
app.config.from_pyfile()
方法傳入一個文件名,通常是以.py結尾的文件,但也不限於只使用.py后綴的文件。
通過導入Python文件的形式導入配置文件:
if __name__ == '__main__':
app.config.from_pyfile('config.py')
app.run()
from_pyfile()
方法有一個silent參數,設置為True時,如果配置文件不存在也不會報錯;
不僅支持Python格式的配置文件,也支持.ini等格式。
五、URL與函數的映射
從前面的例子中,我們可以看到,一個URL要與執行函數進行映射,使用的是@app.route
裝飾器。
@app.route
裝飾器中,可以指定URL的規則來進行更加詳細的映射,比如現在要映射一個文章詳情的URL,文章詳情的URL是/article/id/
,id有可能為1、2、3…,那么可以通過以下方式:
@app.route('/article/<id>')
def article(id):
return '%s article detail' % id
其中,尖括號是固定語法,表示地址中傳入的參數,默認的數據類型是字符串。
如果需要限制參數類型,則要寫成converter:variable
,其中converter是類型名稱,可以有以下幾種:
- string
默認的數據類型,接受任何沒有斜杠/的字符串。 - int
整型。 - float
浮點型。 - path
和string類似,但是可以傳遞斜杠/。 - uuid
uuid類型的字符串。 - any
可以同時指定多種路徑。
新增路由域函數映射測試:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'hello world'
@app.route('/corley')
def hello_corley():
return '這是我的第一個Flask頁面'
if __name__ == '__main__':
app.run(debug=True)
顯示:
顯然,在地址中訪問http://127.0.0.1/corley可以訪問到,因為在flask中已經定義了。
同時還可以動態傳入參數:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'hello world'
@app.route('/corley')
def hello_corley():
return '這是我的第一個Flask頁面'
@app.route('/list/<aid>')
def article_list(aid):
return '這是第{}篇文章'.format(aid)
if __name__ == '__main__':
app.run(debug=True)
顯示:
顯然,因為未定義 /list 所以不能訪問http://127.0.0.1:5000/list;
可以根據傳入的參數動態顯示視圖,但是並未對數據類型進行限制,可以增加對數據類型的限制:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'hello world'
@app.route('/corley')
def hello_corley():
return '這是我的第一個Flask頁面'
@app.route('/list/<int:aid>')
def article_list(aid):
return '這是第{}篇文章'.format(aid)
if __name__ == '__main__':
app.run(debug=True)
顯示:
顯然,此時參數只能是整型數字了。
一般情況下參數中不能含有 /,要想含有 /,必須限制為path類型,除了此區別,path與string類型基本一樣。
示例如下:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'hello world'
@app.route('/corley')
def hello_corley():
return '這是我的第一個Flask頁面'
@app.route('/list/<int:aid>')
def article_list(aid):
return '這是第{}篇文章'.format(aid)
@app.route('/list/<path:aid>')
def comment_list(aid):
return '這是第{}個評論'.format(aid)
if __name__ == '__main__':
app.run(debug=True)
顯示:
顯然,如果參數為數字時,匹配article_list(aid)
函數,如果為字符串類型或者參數中含有 / 時匹配comment_list(aid)
函數。
訪問兩個路徑用同一個函數可以用any來限制,示例如下:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'hello world'
@app.route('/corley')
def hello_corley():
return '這是我的第一個Flask頁面'
@app.route('/list/<int:aid>')
def article_list(aid):
return '這是第{}篇文章'.format(aid)
@app.route('/list/<path:aid>')
def comment_list(aid):
return '這是第{}個評論'.format(aid)
@app.route('/<any(notice,follow):url_path>/')
def message(url_path):
return '當前路徑是'.format(url_path)
if __name__ == '__main__':
app.run(debug=True)
顯示:
此時需要在路由路徑最后添加 / 才能正常訪問。
如果不想指定子路徑來傳遞參數,也可以通過 ?= 的形式來傳遞參數,例如:/article?id=xxx
,這種情況下,可以通過request.args.get('id')
來獲取id的值。如果是post方法,則可以通過request.form.get('id')
來進行獲取。
在flask中添加這類的地址參數需要先從flask中導入request,示例如下:
from flask import Flask
from flask import request
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'hello world'
@app.route('/corley')
def hello_corley():
return '這是我的第一個Flask頁面'
@app.route('/list/<int:aid>')
def article_list(aid):
return '這是第{}篇文章'.format(aid)
@app.route('/list/<path:aid>')
def comment_list(aid):
return '這是第{}個評論'.format(aid)
@app.route('/<any(notice,follow):url_path>/')
def message(url_path):
return url_path
@app.route('/wd')
def baidu_search():
return request.args.get('keyword')
if __name__ == '__main__':
app.run(debug=True)
顯示:
顯然,如果傳入的參數未在函數中定義,會報錯。