Flask初探一(Flask 各參數的應用)

Flask簡介

Flask是什么

Flask 是一個 Python 實現的 Web 開發微框架,
輕量級Web 開發框架

Flask 依賴兩個外部庫: Jinja2 模板引擎和 Werkzeug WSGI 工具集

虛擬環境[1]

作用
虛擬環境可以搭建獨立的python運行環境, 使得單個項目的運行環境與其它項目互不影響.

Hello Flask

一個最小的Flask[2]

# 從flask 模塊導入Flask 類
from flask import Flask

# 得到Flask 類的實例對象 app
app = Flask(__name__)

# 使用路由 為URL 綁定視圖函數
@app.route('/')
def hello_world():
    return 'Hello World!'

if __name__ == '__main__':

    # 運行
    app.run()

引用

  1. 首先,我們導入了 Flask 類。這個類的實例將會是我們的 WSGI 應用程序。
  2. 接下來,我們創建一個該類的實例,第一個參數是應用模塊或者包的名稱。 如果你使用單一的模塊(如本例),你應該使用 name ,因為模塊的名稱將會因其作為單獨應用啟動還是作為模塊導入而有不同( 也即是 'main' 或實際的導入名)。這是必須的,這樣 Flask 才知道到哪去找模板、靜態文件等等。詳情見 Flask 的文檔。
  3. 然后,我們使用 route() 裝飾器告訴 Flask 什么樣的URL 能觸發我們的函數。這個函數的名字也在生成 URL 時被特定的函數采用,這個函數返回我們想要顯示在用戶瀏覽器中的信息。
  4. 最后我們用 run() 函數來讓應用運行在本地服務器上。 其中 if name == 'main': 確保服務器只會在該腳本被 Python 解釋器直接執行的時候才會運行,而不是作為模塊導入的時候。

探究Flask類

import_name 與root_path

flask 類的 init魔法方法

# 基于1.0.2 版本
# Flask __init__ 魔法方法
class Flask(_PackageBoundObject):
    # ...(省略)...
    
      def __init__(
            self,
            import_name,
            static_url_path=None,
            static_folder='static',
            static_host=None,
            host_matching=False,
            subdomain_matching=False,
            template_folder='templates',
            instance_path=None,
            instance_relative_config=False,
            root_path=None
    ):
        _PackageBoundObject.__init__(
            self,
            import_name,
            template_folder=template_folder,
            root_path=root_path
        )

        if static_url_path is not None:
            self.static_url_path = static_url_path

        if static_folder is not None:
            self.static_folder = static_folder

        if instance_path is None:
            instance_path = self.auto_find_instance_path()
        elif not os.path.isabs(instance_path):
            raise ValueError(
                'If an instance path is provided it must be absolute.'
                ' A relative path was given instead.'
            )
 
        self.instance_path = instance_path
 
        self.config = self.make_config(instance_relative_config)
 
        self.view_functions = {}
 
        self.error_handler_spec = {}
 
        self.url_build_error_handlers = []
 
        self.before_request_funcs = {}
 
        self.before_first_request_funcs = []
 
        self.after_request_funcs = {}
 
        self.teardown_request_funcs = {}
 
        self.teardown_appcontext_funcs = []
 
        self.url_value_preprocessors = {}
 
        self.url_default_functions = {}
 
        self.template_context_processors = {
            None: [_default_template_ctx_processor]
        }
 
        self.shell_context_processors = []

        self.blueprints = {}
        self._blueprint_order = []
 
        self.extensions = {}

        self.url_map = Map()

        self.url_map.host_matching = host_matching
        self.subdomain_matching = subdomain_matching

        self._got_first_request = False
        self._before_request_lock = Lock()

        if self.has_static_folder:
            assert bool(static_host) == host_matching, 'Invalid static_host/host_matching combination'
            self.add_url_rule(
                self.static_url_path + '/<path:filename>',
                endpoint='static',
                host=static_host,
                view_func=self.send_static_file
            )

        self.cli = cli.AppGroup(self.name)

        # ...(省略)...

從上面可以看出Flask 繼承自_PackageBoundObject 類,在Flask 的init 魔法方法中調用了父類_PackageBoundObject init 魔法方法.

_PackageBoundObject 類

class _PackageBoundObject(object):

    # ...(省略)...
    def __init__(self, import_name, template_folder=None, root_path=None):
        self.import_name = import_name
        self.template_folder = template_folder

        if root_path is None:
            root_path = get_root_path(self.import_name)

        self.root_path = root_path
        self._static_folder = None
        self._static_url_path = None

    # ...(省略)...

flask 通過調用父類_PackageBoundObject 初始化方法設置import_name / template_folder / root_path 實例屬性的值. root_path 屬性的值是使用import_name 屬性作為參數,調用get_root_path方法得到的.

get_root_path

def get_root_path(import_name):
    """Returns the path to a package or cwd if that cannot be found.  This
    returns the path of a package or the folder that contains a module.

    Not to be confused with the package path returned by :func:`find_package`.
    """
    # Module already imported and has a file attribute.  Use that first.
    mod = sys.modules.get(import_name)
    print(mod)
    if mod is not None and hasattr(mod, '__file__'):
        return os.path.dirname(os.path.abspath(mod.__file__))
        
    # ...(省略)...

# 實驗
# print("# *" * 20)
# print()
# print(sys.modules.get("__main__"))
# print(sys.modules.get(__name__).__dict__)
# print(hasattr(sys.modules.get(__name__), '__file__'))
# print(os.path.abspath(sys.modules.get(__name__).__file__))
# print(os.path.dirname(os.path.abspath(sys.modules.get(__name__).__file__)))
# print()
# print("# *" * 20)

# 實驗結果  
# # *# *# *# *# *# *# *# *# *# *# *# *# *# *# *# *# *# *# *# *
# 
# <module '__main__' from 'E:/workspace/git/flask/demo/demo.py'>
# {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x017C5630>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'E:/workspace/git/flask/demo/demo.py', '__cached__': None, 'os': <module 'os' from 'D:\\software\\Python\\lib\\os.py'>, 'sys': <module 'sys' (built-in)>, 'Flask': <class 'flask.app.Flask'>, 'app': <Flask 'demo'>, 'index': <function index at 0x036235D0>}
# True
# E:\workspace\git\flask\demo\demo.py
# E:\workspace\git\flask\demo
# 
# # *# *# *# *# *# *# *# *# *# *# *# *# *# *# *# *# *# *# *# *

通過這里可以更直觀的看出 root_path 得到的是主模塊所在的目錄的絕對路徑

static_url_path與static_folder

參數注釋

:param static_url_path: can be used to specify a different path for the
                            static files on the web.  Defaults to the name
                            of the `static_folder` folder.
:param static_folder: the folder with static files that should be served
                          at `static_url_path`.  Defaults to the ``'static'``
                          folder in the root path of the application.

實驗

demo.py

from flask import Flask, render_template

app = Flask(__name__, static_url_path="/stat",
            static_folder='static')


@app.route('/')
def index():
    return render_template("index.html")


if __name__ == '__main__':
    app.run(debug=True)

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <script type="text/javascript" src="../stat/js/main.js"></script>
</head>
</html>

main.js

window.onload = function () {
    alert("I'm DragonFang");
};

實驗結果

# 127.0.0.1 - - [12/Jun/2018 09:39:48] "GET / HTTP/1.1" 200 -
# 127.0.0.1 - - [12/Jun/2018 09:39:48] "GET /stat/js/main.js HTTP/1.1" 200 -
# 127.0.0.1 - - [12/Jun/2018 09:40:09] "GET /static/js/main.js HTTP/1.1" 404 -

測試目錄結構


01-測試目錄結構.png

通過測試以及目錄結構可以得出, 當static_url_path 和 static_folder 同時存在時, static_url_path代替static_folder 指明了靜態資源的路徑

static_url_path / static_folder / static_host / host_matching

# :param static_host: the host to use when adding the static route.
#     Defaults to None. Required when using ``host_matching=True``
#     with a ``static_folder`` configured.
# :param host_matching: set ``url_map.host_matching`` attribute.
#     Defaults to False.
     
     # init魔法方法部分
     if self.has_static_folder:
            assert bool(static_host) == host_matching, 'Invalid static_host/host_matching combination'
            self.add_url_rule(
                self.static_url_path + '/<path:filename>',
                endpoint='static',
                host=static_host,
                view_func=self.send_static_file
            )

    # add_url_rule 方法
    @setupmethod
    def add_url_rule(self, rule, endpoint=None, view_func=None,
                     provide_automatic_options=None, **options):
         
        if endpoint is None:
            endpoint = _endpoint_from_view_func(view_func)
        options['endpoint'] = endpoint
        methods = options.pop('methods', None)

        if methods is None:
            methods = getattr(view_func, 'methods', None) or ('GET',)
        if isinstance(methods, string_types):
            raise TypeError('Allowed methods have to be iterables of strings, '
                            'for example: @app.route(..., methods=["POST"])')
        methods = set(item.upper() for item in methods)
 
        required_methods = set(getattr(view_func, 'required_methods', ()))
 
        if provide_automatic_options is None:
            provide_automatic_options = getattr(view_func,
                                                'provide_automatic_options', None)

        if provide_automatic_options is None:
            if 'OPTIONS' not in methods:
                provide_automatic_options = True
                required_methods.add('OPTIONS')
            else:
                provide_automatic_options = False
 
        methods |= required_methods

        rule = self.url_rule_class(rule, methods=methods, **options)
        rule.provide_automatic_options = provide_automatic_options

        self.url_map.add(rule)
        if view_func is not None:
            old_func = self.view_functions.get(endpoint)
            if old_func is not None and old_func != view_func:
                raise AssertionError('View function mapping is overwriting an '
                                     'existing endpoint function: %s' % endpoint)
            self.view_functions[endpoint] = view_func

實驗

demo.py

from flask import Flask, render_template, request

# host_matching 和 static_host 組合更改靜態資源的訪問地址(主機:端口)
# 結合 static_url_path 指定文件 
app = Flask(__name__, host_matching=True, static_host="192.168.70.48:13579", static_url_path="/stat")


@app.route('/', host="127.0.0.1:13579")
def index():
    print(app.url_map)
    print(app.url_map.host_matching)
    return render_template("index.html")


@app.errorhandler(404)
def catch_404(error):
    print()
    print("#" * 50)
    print(app.url_map)
    print(app.url_map.host_matching)
    return request.url + "\n error %s" % 404, 404


if __name__ == '__main__':
    app.run(debug=True, host="0.0.0.0", port=13579)

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    {#  <base> 標簽為頁面上的所有鏈接規定默認地址或默認目標。  #}
    <base />
    <base target="_blank"/>
    <meta charset="UTF-8">
    <script type="text/javascript" src="../stat/js/main.js"></script>
</head>
</html>

實驗結果

Map([<Rule '127.0.0.1:13579|/' (OPTIONS, HEAD, GET) -> index>,
 <Rule '192.168.70.48:13579|/stat/<filename>' (OPTIONS, HEAD, GET) -> static>])
True
127.0.0.1 - - [14/Jun/2018 10:59:43] "GET / HTTP/1.1" 200 -

通過實驗可以發現 static_url_path / static_folder / static_host / host_matching 四者結合使用可以訪問資源服務器上的指定文件夾下的資源

subdomain_matching

官方文檔

consider the subdomain relative to SERVER_NAME when matching routes. Defaults to False.

機翻 : 在匹配路由時,考慮相對于server_name的子域。默認為false。

實驗一 (無subdomain_matching)

from flask import Flask, render_template, request, url_for

app = Flask(__name__)
app.config["SERVER_NAME"] = "test.dev:13579"


@app.route('/')
def index():
    print(request.url)
    print(app.url_map)
    return render_template("index.html")


@app.route("/", subdomain="blog")
def blog_home():
    print(request.url)
    print(app.url_map)
    return render_template("index.html")


@app.errorhandler(404)
def catch_404(error):
    print()
    print("#" * 50)
    print(app.url_map)
    print(app.url_map.host_matching)
    return request.url + "\n error %s" % 404, 404


if __name__ == '__main__':
    app.run(debug=True, host="0.0.0.0", port=13579)

實驗結果

# http://test.dev:13579/
# Map([<Rule 'blog|/' (GET, HEAD, OPTIONS) -> blog_home>,
#  <Rule '/' (GET, HEAD, OPTIONS) -> index>,
#  <Rule '/static/<filename>' (GET, HEAD, OPTIONS) -> static>])
# 192.168.70.48 - - [14/Jun/2018 20:57:27] "GET / HTTP/1.1" 200 -
# 
# 
# http://blog.test.dev:13579/
# Map([<Rule 'blog|/' (GET, HEAD, OPTIONS) -> blog_home>,
#  <Rule '/' (GET, HEAD, OPTIONS) -> index>,
#  <Rule '/static/<filename>' (GET, HEAD, OPTIONS) -> static>])
# 192.168.70.48 - - [14/Jun/2018 20:57:39] "GET / HTTP/1.1" 200 -

實驗二 (有subdomain_matching)

from flask import Flask, render_template, request, url_for

# 設置subdomain_matching=True 
app = Flask(__name__, subdomain_matching=True)
app.config["SERVER_NAME"] = "test.dev:13579"


@app.route('/')
def index():
    print(request.url)
    print(app.url_map)
    return render_template("index.html")


@app.route("/", subdomain="blog")
def blog_home():
    print(request.url)
    print(app.url_map)
    return render_template("index.html")


@app.errorhandler(404)
def catch_404(error):
    print()
    print("#" * 50)
    print(app.url_map)
    print(app.url_map.host_matching)
    return request.url + "\n error %s" % 404, 404


if __name__ == '__main__':
    app.run(debug=True, host="0.0.0.0", port=13579)

實驗結果

# http://test.dev:13579/
# Map([<Rule 'blog|/' (GET, HEAD, OPTIONS) -> blog_home>,
#  <Rule '/' (GET, HEAD, OPTIONS) -> index>,
#  <Rule '/static/<filename>' (GET, HEAD, OPTIONS) -> static>])
# 192.168.70.48 - - [14/Jun/2018 20:57:27] "GET / HTTP/1.1" 200 -
# 
# 
# http://blog.test.dev:13579/
# Map([<Rule 'blog|/' (GET, HEAD, OPTIONS) -> blog_home>,
#  <Rule '/' (GET, HEAD, OPTIONS) -> index>,
#  <Rule '/static/<filename>' (GET, HEAD, OPTIONS) -> static>])
# 192.168.70.48 - - [14/Jun/2018 20:57:39] "GET / HTTP/1.1" 200 -

通過實驗并未發現subdomain_matching 為True與 False的區別.##TODO 待解

template_folder

官方文檔

the folder that contains the templates that should
be used by the application. Defaults to
'templates' folder in the root path of the
application.

用于設置模板文件存放的文件夾的名字

實驗

from flask import Flask, render_template, request, url_for

app = Flask(__name__, template_folder="temp")


@app.route('/')
def index():
    print(request.url)
    print(app.url_map)
    return render_template("index.html")


@app.errorhandler(404)
def catch_404(error):
    print()
    print("#" * 50)
    print(app.url_map)
    print(app.url_map.host_matching)
    return request.url + "\n error %s" % 404, 404


if __name__ == '__main__':
    app.run(debug=True, host="0.0.0.0", port=13579)

項目目錄


02-測試目錄結構.png

實驗結果

# http://127.0.0.1:13579/
# Map([<Rule '/' (OPTIONS, HEAD, GET) -> index>,
#  <Rule '/static/<filename>' (OPTIONS, HEAD, GET) -> static>])
# 127.0.0.1 - - [14/Jun/2018 21:21:29] "GET / HTTP/1.1" 500 -
# Traceback (most recent call last):
#
#   ...(省略)...
#
#   File "E:\workspace\git\flask\flask\templating.py", line 86, in _get_source_fast
#     raise TemplateNotFound(template)
# jinja2.exceptions.TemplateNotFound: index.html

instance_path 和 instance_relative_config

官方文檔

:param instance_path: An alternative instance path for the application.
??By default the folder 'instance' next to the package or module is assumed to be the instance path.


:param instance_relative_config: if set to True relative filenames
??for loading the config are assumed to be relative to the instance path
??instead of the application root.

實驗 instance_relative_config=True

from flask import Flask, render_template, request, url_for

app = Flask(__name__, instance_relative_config=True) 

app.config.from_pyfile("config.py")


@app.route('/')
def index():
    print(app.instance_path)
    return render_template("index.html")


@app.errorhandler(404)
def catch_404(error):
    print()
    return request.url + "\n error %s" % 404, 404


if __name__ == '__main__':
    app.run(host="0.0.0.0", port=13579)

實驗結果

Traceback (most recent call last):
  File "E:/workspace/git/flask/demo/demo.py", line 6, in <module>
    app.config.from_pyfile("config.py")
  File "E:\workspace\git\flask\flask\config.py", line 129, in from_pyfile
    with open(filename, mode='rb') as config_file:
FileNotFoundError: [Errno 2] Unable to load configuration file (No such file or directory): 'E:\\workspace\\git\\flask\\demo\\instance\\config.py'

無法從E:\workspace\git\flask\demo\instance\ 路徑下加載配置實例

實驗 instance_relative_config 和 instance_path
兩者配合從版本控制外加載配置信息

from flask import Flask, render_template, request, url_for

app = Flask(__name__, instance_path="E:\\workspace\\", instance_relative_config=True)

app.config.from_pyfile("config.py")


@app.route('/')
def index():
    print(app.instance_path)
    return render_template("index.html")


@app.errorhandler(404)
def catch_404(error):
    print()
    return request.url + "\n error %s" % 404, 404


if __name__ == '__main__':
    app.run(host="0.0.0.0", port=13579)

config.py

DEBUG = True

實驗結果

D:\software\Python\python.exe E:/workspace/git/flask/demo/demo.py

 * Serving Flask app "demo" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Restarting with stat

 * Debugger is active!
 * Debugger PIN: 321-943-347
 * Running on http://0.0.0.0:13579/ (Press CTRL+C to quit)

總結:

  • import_name: 應用程序的另一種實例路徑。默認情況下,包或模塊旁邊的文件夾 instance 被假定為實例路徑。
  • root_path: 默認情況下,flask將自動計算引用程序根的絕對路徑, 由import_name 決定.
  • instance_path 和 instance_relative_config 共同作用,可以改變由import_name 實例路徑, 掩藏敏感配置[3]
  • static_folder 指定了靜態資源的路徑. 默認情況下,底層實際上是通過static_folder 確定了 static_url_path,
    然后通過 self.static_url_path + '/<path:filename>'注冊的靜態資源路由.
  • 當static_url_path 和 static_folder 同時存在時, 系統會直接使用 self.static_url_path + '/<path:filename>'注冊的靜態資源路由.
  • static_host 和 host_matching 同時作用可以改變靜態資資源存放的主機, 既可以從資源服務器讀取資源.
  • static_url_path / static_folder / static_host / host_matching 四者結合使用可以訪問資源服務器上的指定文件夾下的資源
  • template_folder 設置模板文件
  • subdomain_matching 支持子域名, 結合app.config["SERVER_NAME"] = "域名:端口" 使用.

TODO遺留問題

通過實驗并未發現subdomain_matching 為True與 False的區別.##TODO 待解


到此結? DragonFangQy 2018.6.15


  1. 虛擬環境安裝 ?

  2. 一個最小的Flask應用 ?

  3. 實例文件夾 ?

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容