Flask 基礎知識總結

  • 裝飾器
  • 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 %}
&copy; 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章