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 href="http://192.168.70.48:13579"/>
    <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. 实例文件夹

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容