- 裝飾器
- Flask安裝
- Routing
- HTTP Method
- 靜態和模板
- Request/Response
- Error/重定向
- Flash Message
- Logger
- Flask-Script
裝飾器
def log(level, *arg, **kvargs):
def inner(func):
"""
* 無名參數,用來傳遞任意個無名參數,這些參數會以一個Tuple的形式訪問
** 有名參數 用來處理傳遞任意個有名字的參數,這些參數用dict來訪問
"""
def wrapper(*args, **kvargs):
print(level, 'before calling ', func.__name__)
print(level, 'args',args,'kvargs', kvargs)
func(*args, **kvargs)
print(level, 'end calling ', func.__name__)
return wrapper
return inner
@log(level = 'INFO') # 直接去調用log裝飾函數
def hello(name, age):
print('hello',name,age)
if __name__ == '__main__':
hello(name='toohoo',age=2) #= log(hello(name='toohoo',age=2))
print("---------------------------")
log(hello(name='toohoo',age=2))
測試結果
INFO before calling hello
INFO args () kvargs {'name': 'toohoo', 'age': 2}
hello toohoo 2
INFO end calling hello
---------------------------
INFO before calling hello
INFO args () kvargs {'name': 'toohoo', 'age': 2}
hello toohoo 2
INFO end calling hello
從上面的結果可以看出執行的順序是從__main__開始調用Hello==>log裝飾器==>inner函數==>wrapper函數==>func(就是hello函數)==>最後輸出end…
因此可以知道,使用裝飾器可以簡化操作
安裝與驗證
使用命令import flask
驗證是否安裝了flask,沒有安裝時候可以使用命令安裝
sudo pip3 install flask
參考文檔:
- 中文文檔:http://dormousehole.readthedocs.io/en/latest/
- github開源地址:https://github.com/pallets/flask
- 最新版本官網:https://palletsprojects.com/p/flask/
驗證簡單app
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'hello'
if __name__ == '__main__':
app.run(debug=True) # 開發的時候打開debug模式
路由 Routing
- 1、
/
結尾自動補齊:@app.route('/')
- 2、多映射:
@app.route('/index/')
- 3、參數變量:
@app.route('/profile/<uid>/')
- 4、變量類型:
@app.route('/profile/<int:uid>/')
1、/
結尾自動補齊和多映射:當使用瀏覽器訪問的時候
使用127.0.0.1:5000和127.0.0.1:5000/都是一樣的,/會自動補齊。
多映射就是可以設置多個映射路由,如下面的代碼:
from flask import Flask
app = Flask(__name__)
@app.route('/index/')
@app.route('/')
def index():
return 'hello'
if __name__ == '__main__':
app.run(debug=True)
使用127.0.0.1:5000和127.0.0.1:5000/和127.0.0.1:5000/index都是可以訪問的。
2、參數變量:@app.route('/profile/<uid>/')
@app.route('/profile/<uid>')
def profile(uid):
return 'haha' + uid
瀏覽器中請求的url之後加上一個uid,頁面會返回一個對應的uid,不限類型。
3、變量類型:@app.route('/profile/<int:uid>/')
@app.route('/profile/<int:uid>')
def profile(uid):
return 'haha' + str(uid)
這個就必須是int類型,否則瀏覽器會顯示Not Found
HTTP Method
- GET:獲取接口信息
- HEAD:緊急查看HTTP的頭
- POST:提交數據到服務器
- PUT:支持冪等性的POST(函數重發還是那一次,不改變)
- DELETE:刪除服務器上的資源
- OPITIONS:查看支持的方法
注意:一般的網站只用GET和POST,代表獲取和更新,html的form僅僅支持GET和POST
@app.route('/profile/<int:uid>',methods=['GET', 'post'])
def profile(uid):
return 'haha' + str(uid)
可以使用瀏覽器插件postman實現進行請求,當使用post請求Get的時候會返回405
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>405 Method Not Allowed</title>
<h1>Method Not Allowed</h1>
<p>The method is not allowed for the requested URL.</p>
靜態和模板
靜態:
- 默認的目錄是:static/templates
- 文件:css/js/images
模板文件:
- 默認目錄:templates
- 文件:x.html,x.htm 隨意
jinjia2的模板語法
- {{ 變量/表達式}}
- {% 語法 %}
- { # 註釋 # }
- 以
#
開頭,行語法表達式(line statements),需要在後臺文件裏面加上對應的語法 - app.jinja_env.line_statement_prefix = ‘#’
官方文檔:
http://jinja.pocoo.org/docs/dev/templates/
http://docs.jinkan.org/docs/jinja2/
例如有後臺的數據:
@app.route('/profile/<int:uid>',methods=['GET', 'post'])
def profile(uid):
colors = ('red','green','red','green')
infos = {'toohoo':'abc','zhihu':'def'}
return render_template('profile.html',uid=uid,colors=colors,infos=infos)
for 循環
以花括號和雙百分號對稱組合形成有效的表達邏輯
profile: {{ uid }} <br>
<!--註釋-->
{# you can't see me ~ #}
{% for i in range(0,5):%}
profile: {{ i }}<br>
{% endfor %}
<!--注意,需要加上聲明app.jinja_env.line_statement_prefix = '#'才能應用-->
<ul>
# for color in colors:
<li>{{ color }}</li>
# endfor
</ul>
<br>
對於內嵌循環序號loop
{% for color in colors: %}
{{ loop.index }} {{ color }} <br>
{% endfor %}
loop.index
是從1開始的;
loop.index0
是從0開始的;
loop.first
判斷是否是第一位,是就爲True,其餘的爲False
loop.cycle
循環輸出所有的值
filters
<!--將Info的信息項過濾爲大寫的形式-->
{% filter upper %}
<!--遍歷infos -->
{% for k,v in infos.items(): %}
{{ k }}, {{ v }} <br>
{% endfor %}
{% endfilter %}
將小寫轉成大寫:
TOOHOO, ABC
ZHIHU, DEF
call/macro宏調用
<!--使用宏調用-->
{% macro render_color(color) %}
<!--注意這裏包含有兩個值,需要設置一個回調{{ caller() }}-->
<div>This is color {{ color }}</div>{{ caller() }}
{% endmacro %}
{% for color in colors: %}
{% call render_color(color) %}
render_color_demo
{% endcall %}
{% endfor %}
顯示的結果爲:
his is color red
render_color_demo
This is color green
render_color_demo
This is color red
render_color_demo
This is color green
render_color_demo
模板繼承:
include,extend
例如孩子繼承父親,用base.html和child.html兩個文件,則他們的繼承關係可以使用incline和extend來達成:
base.html
base.html
<!DOCTYPE html>
<html lang="en">
<head>
{% block head %}
<link rel="stylesheet" href="style.css" />
<title>{% block title %}{% endblock %} - My Webpage</title>
{% endblock %}
</head>
<body>
<div id="content">{% block content %}{% endblock %}</div>
<div id="footer">
{% block footer %}
© Copyright 2008 by <a href="http://domain.invalid/">you</a>.
{% endblock %}
</div>
</body>
</html>
child.html
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{{ super() }}
<style type="text/css">
.important { color: #336699; }
</style>
{% endblock %}
{% block content %}
<h1>Index</h1>
<p class="important">
Welcome to my awesome homepage.
</p>
{% endblock %}
可以參考對應的項目:中base.html和index.html等文件寫法:
https://github.com/too-hoo/myinstagram/tree/master/myinstagram/templates
Request/Response
request
- 參數解析
- cookie
- http請求字段
- 文件上傳
response
- 頁面內容返回
- cookie下發
- http字段設置,headers
@app.route('/request')
def request_demo():
key = request.args.get('key', 'defaultkey')
res = request.args.get('key', 'defaultkey') + '<br>'
res = res + request.url + '++' + request.path + '<br>'
for property in dir(request): # 遍歷request裏面的方法使用haha字符串連接起來
res = res + str(property) + '<br>haha' + str(eval('request.' + property)) + '<br>haha'
response = make_response(res)
response.set_cookie('toohooid', key) # 這是名字爲toohooid的鍵爲默認的值defaultkey的cookie
response.status = '404' # 使用F12 開發者工具可以查看狀態
response.headers['toohoo'] = 'hello~~' # 在頭部添加一個鍵值對
return response
重定向和ERROR錯誤
重定向:狀態碼含義如下:
301:永久轉移:Status Code: 301 MOVED PERMANENTLY (from disk cache)
302:臨時轉移:Status Code: 302 FOUND
@app.route('/newpath')
def newpath():
return 'newpath' # 解析路徑並返回路徑
@app.route('/re/<int:code>') # 重定向例子
def redirect_demo(code):
return redirect('/newpath', code=code)
可以使用瀏覽器分別使用301和302進行驗證
Error和異常處理
@app.errorhandler(400)
def exception_page(e):
response = make_response('出錯啦~')
return response
@app.errorhandler(404)
def page_not_found(error):
return render_template('not_found.html', url=request.url), 404
@app.route('/admin')
def admin():
if request.args['key'] == 'admin':
return 'hello admin'
else: # 不帶參數的時候會走這裏,這裏拋出的異常會被exception_page,400,捕捉
raise Exception()
not_found.html的內容爲:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Not Found</title>
</head>
<body>
<h2>你來到沒有知識的荒原{{ url }}</h2>
</body>
</html>
- 當使用http://127.0.0.1:5000/guest訪問的時候,頁面返回:你來到沒有知識的荒原http://127.0.0.1:5000/guest
- 當使用http://127.0.0.1:5000/admin訪問的時候,頁面返回:出錯啦!
- 當使用http://127.0.0.1:5000/admin?key=admin訪問的時候,頁面返回:hello admin
Flash Message
發送
- 獲取接口信息,flash(msg)
獲取 - get_flashed_messages
屬性 - 通過session傳遞消息
目的是向用戶反饋信息,先在文件頭引入secretkey來保證一致性
# 在進行flash顯示的時候要先設置一個secretkey保證統一
app.secret_key = 'toohoo'
@app.route('/index/')
@app.route('/')
def index():
res = ''
for msg, category in get_flashed_messages(with_categories=True):
res = res + category + msg + '<br>haha'
res += 'hello'
return res
#flash處理
@app.route('/login')
def login():
app.logger.info('login success')
flash('登錄成功', 'info.txt')
# return 'ok'
return redirect('/') 跳轉到首頁
輸出的內容爲:
登錄成功info.txt
haha登錄成功info.txt
hahahello
日誌處理
debug的終極方案:按照不同的等級,將日誌打印出來:(這裏有個版本坑注意點,往後看)
import logging
from logging.handlers import RotatingFileHandler
@app.route('/log/<level>/<msg>/')
def log(level, msg):
dict = {'warn': logging.WARN, 'error': logging.ERROR, 'info': logging.INFO}
if level in dict.keys():
app.logger.log(dict[level], msg)
return 'logged:' + msg
def set_logger():
info_file_handler = RotatingFileHandler('./logs/info.txt')
info_file_handler.setLevel(logging.INFO)
app.logger.addHandler(info_file_handler)
warn_file_handler = RotatingFileHandler('./logs/warn.txt')
warn_file_handler.setLevel(logging.WARN)
app.logger.addHandler(warn_file_handler)
error_file_handler = RotatingFileHandler('./logs/error.txt')
error_file_handler.setLevel(logging.ERROR)
app.logger.addHandler(error_file_handler)
if __name__ == '__main__':
set_logger()
app.run(debug=True)
上面的是python2.7版本的日誌輸出的最佳簡單實踐了,但是到Python3.6和Python3.7的時候,當使用上面的配置文件的時候,使用瀏覽器訪問路徑http://127.0.0.1:5000/log/info/infomsg/
的時候,info.txt文件是死活不肯輸出infomsg,原來是升級改版了,需要在文件中set_logger函數前面加上logging.basicConfig(level=logging.DEBUG)
,下面是整合的代碼:
def set_logger():
logging.basicConfig(level=logging.DEBUG)
info_file_handler = RotatingFileHandler('./logs/info.txt')
info_file_handler.setLevel(logging.INFO)
app.logger.addHandler(info_file_handler)
warn_file_handler = RotatingFileHandler('./logs/warn.txt')
warn_file_handler.setLevel(logging.WARN)
app.logger.addHandler(warn_file_handler)
error_file_handler = RotatingFileHandler('./logs/error.txt')
error_file_handler.setLevel(logging.ERROR)
app.logger.addHandler(error_file_handler)
這樣就可以在info.txt
中輸出infomsg
,並且日誌會安照不同的等級輸出:
- http://127.0.0.1:5000/log/info/infomsg/ 訪問的時候,infomsg只會在info.txt中出現
- http://127.0.0.1:5000/log/warn/warnmsg/ 訪問的時候,warnmsg會在info.txt和warn.txt中出現
- http://127.0.0.1:5000/log/error/errormsg/ 訪問的時候,errormsg會在info.txt、warn.txt和error.txt中出現
參考文檔:
- https://docs.python.org/3/howto/logging.html#logging-basic-tutorial
但是能不能讓格式變得好看一些,文件大小能否限制一下,所以可以設置一下,將上面的代碼改造一下:
def set_logger():
logging.basicConfig(level=logging.DEBUG)
formatter = logging.Formatter('%(asctime)s %(levelname)s %(filename)s %(lineno)d %(message)s')
info_log_handler = RotatingFileHandler(filename='./logs/info.txt', maxBytes=1024*1024, backupCount=10)
info_log_handler.setLevel(logging.INFO)
info_log_handler.setFormatter(formatter)
app.logger.addHandler(info_log_handler)
warn_log_handler = RotatingFileHandler(filename='./logs/warn.txt', maxBytes=1024 * 1024, backupCount=10)
warn_log_handler.setLevel(logging.WARN)
warn_log_handler.setFormatter(formatter)
app.logger.addHandler(warn_log_handler)
error_log_handler = RotatingFileHandler(filename='./logs/error.txt', maxBytes=1024 * 1024, backupCount=10)
error_log_handler.setLevel(logging.ERROR)
error_log_handler.setFormatter(formatter)
app.logger.addHandler(error_log_handler)
輸出的結果如下:
2019-08-14 11:57:21,269 INFO hello.py 91 infomsg
2019-08-14 11:57:49,555 WARNING hello.py 91 warnmsg
2019-08-14 11:58:09,429 ERROR hello.py 91 errormsg
Flask-Script
安裝: pip3 install flask-script
官網地址爲:https://flask-script.readthedocs.io/en/latest/
注: 維護者在新版本的Flask已經內置了一個CLI工具,此工具用的僅僅作爲輔助,使用內置的或許更加適合。
通過腳本的方式將項目運行起來,類似於cli程序,輔助工程做一些周邊的工作,使得大項目開發和運行變得更加簡單,簡單實踐如下:
#!/usr/bin/env python3
# -*-encoding:UTF-8-*-
from flask_script import Manager
from hello import app
manager = Manager(app)
if __name__ == '__main__':
manager.run()
其在當在控制檯運行python3 manger.py的時候會輸出如下信息:
usage: manager.py [-?] {shell,runserver} ...
positional arguments:
{shell,runserver}
shell Runs a Python shell inside Flask application context.
runserver Runs the Flask development server i.e. app.run()
optional arguments:
-?, --help show this help message and exit
可以看出其初始可選擇用法有:
- 1 打開shell,在內置的Flask應用中運行Python;
- 2 運行服務器,以運行Flask的開發服務器
可以選擇的參數有:–help 查看幫助信息
功能添加:
- 爲其添加一些命令,並解析該命令:例如添加一個say hello和初始化數據庫:
#!/usr/bin/env python3
# -*-encoding:UTF-8-*-
from flask_script import Manager
from hello import app
manager = Manager(app)
@manager.option('-n', '--name', dest='name', default='toohoo')
def hello(name):
'say hello'
print('hello', name)
@manager.command
def init_database():
'初始化數據庫'
print('init database...')
if __name__ == '__main__':
manager.run()
再次運行會出現如下信息:
usage: manager.py [-?] {hello,init_database,shell,runserver} ...
positional arguments:
{hello,init_database,shell,runserver}
hello say hello
init_database 初始化數據庫
shell Runs a Python shell inside Flask application context.
runserver Runs the Flask development server i.e. app.run()
optional arguments:
-?, --help show this help message and exit
可見功能明顯增加:
統一使用的命令爲:python3 manager.py + 對應的命令參數
本總結參考代碼地址爲:https://github.com/too-hoo/PythonFoundation