Flask 源码阅读笔记 开篇


Flask 是一个 Python 实现的 Web 开发微框架, 有丰富的生态资源。本文从一段官方的示例代码通过一步步打断点方式解释 Flask 内部的运行机制,在一些关键概念会有相关解释,这些前提概念对整体理解 Flask框架十分重要,本文基于flask 0.1 版本进行相应的分析。


官方demo示例

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

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

第一行import Flask 类对象,这个无需解释。跳到第二行,使用当前模块的名字传入Flask类中,并实例化Flask对象,我们在这个地方打个断点,看看Flask类别里有什么。


Flask debug

上图可以看出,Flask类中定义jinia_options、request_class、response_class等属性,这里我们不关系具体作用,先看看源码中Flask 是不是定义了这些属性。

class Flask(object):
    # 省略了注释部分
    # flask 用作请求对象的类
    request_class = Request
    # flask 用作响应对象的类
    response_class = Response
    # 静态文件路径
    static_path = '/static'
    # 密钥,用于加密 session 或其它涉及安全的东西
    secret_key = None
    #存储session对象数据的cookie名称
    session_cookie_name = 'session'
    # Jinja2环境的一些选项
    jinja_options = dict(
        autoescape=True,
        extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_']
    )

这部分是初始化Flask类中默认设置的一些属性,其实通过名字也可以大概知道每个属性的作用。看到这个地方时是不是一脸懵逼,Request、Response 是什么东西,有什么作用?Jinja2 又是什么东西? 别急,下面慢慢解释这几个东西的作用。

Request && Response

from werkzeug import Request as RequestBase, Response as ResponseBase

class Request(RequestBase):
    """The request object used by default in flask.  Remembers the
    matched endpoint and view arguments.

    It is what ends up as :class:`~flask.request`.  If you want to replace
    the request object used you can subclass this and set
    :attr:`~flask.Flask.request_class` to your subclass.
    """

    def __init__(self, environ):
        RequestBase.__init__(self, environ)
        self.endpoint = None     # 请求对象的端点
        self.view_args = None    # 请求视图函数的参数

class Response(ResponseBase):
    """The response object that is used by default in flask.  Works like the
    response object from Werkzeug but is set to have a HTML mimetype by
    default.  Quite often you don't have to create this object yourself because
    :meth:`~flask.Flask.make_response` will take care of that for you.

    If you want to replace the response object used you can subclass this and
    set :attr:`~flask.Flask.request_class` to your subclass.
    """
    default_mimetype = 'text/html'

通过源码的注释我们可以知道,Request、Response都只是对 werkzeug 库的Request、Response 进行了一层包装并加入一些属性。先说一下它们的作用:

  • Request 的作用:是 flask 默认的请求对象,用来记住匹配的endpoint(端点)和view arguments(视图参数)
  • Response 的作用:是 flask 默认的响应对象,默认设置MIME类型默认设置为HTML(即是定义了内容类型 Content-Type 返回的类型为HTML), 默认情况下,你不用自己创建这个对象,因为下面的 make_response 函数会帮你处理。

看完上面源码和解释,是不是有新的疑问了,werkzeug又是什么?端点又是什么概念?额,werkzeug的作用真的很大,整个框架都是基于它实现的,下面会有一个部分专门说明这个库。说明: werkzeug 库和 jinja2 是 flask 的两个依赖库,会分出一篇文章专门介绍,这篇文章重点是整个 Flask 内部的机制,建议看到对应部分,先提前去读两个依赖库的文章。

接下来继续下一步的调试,初始化一个Flask类, 先看看 Flask 类的初始化函数:


def _get_package_path(name):
    """Returns the path to a package or cwd if that cannot be found."""
    # 获取 模块包 路径,被 Flask 引用
    try:
        return os.path.abspath(os.path.dirname(sys.modules[name].__file__))
    except (KeyError, AttributeError):
        return os.getcwd()

class Flask(object):
      # 简化版,已经去掉注释,建议看源码注释加上这个理解
     def __init__(self, package_name):
        # 设置是否开启调试模式,若开启,会监视项目代码变化,
        # 开发服务器重载 Flask 应用
        self.debug = False

        # 包或模块的名字,模块的名称将会因其作为单独应用启动还是作为模块导入而不同
        # Flask 才知道到哪去找模板、静态文件
        self.package_name = package_name

        # 根据 Flask 传入的__name__, 找到项目的根路径
        self.root_path = _get_package_path(self.package_name)

        # 已注册的所有视图函数的字典,字典的键是函数名称,可以用来生成URL(url_for函数)
        # 字典的值是函数本身, 想要注册视图函数,可以使用 route 装饰器 
        self.view_functions = {}

        # 所有已注册错误处理程序的字典, 字典的键是一个整数类型(integer)的错误码
        # 字典的值是对应错误的函数,想要注册错误handler, 可以使用 errorhandler 装饰器
        self.error_handlers = {}

        # 请求开始进入时,但还请求还没调度前调用的函数列表,也就是预处理操作
        # 可用于打开数据库连接或获取当前登录用户,使用 before_route 装饰器注册
        self.before_request_funcs = []

        # 请求结束时调用的函数列表,这些函数会被传入当前响应对象并将其修改或替换它。
        self.after_request_funcs = []
 
        # 不带参数调用的函数列表,用于填充模板上下文,每个应该返回更新模板上下文的字典
        # 默认的处理器用来注入session、request和g
        self.template_context_processors = [_default_template_ctx_processor]
        
        # 使用 werkzeug 的 routing.Map, 用于给应用增加一些URL规则,
        # URL规则形成一个Map实例的过程中会生成对应的正则表达式,可以进行URL匹配
        self.url_map = Map()
        
        # 添加静态文件的URL映射规则
        # SharedDataMiddleware中间件用来为程序添加处理静态文件的能力
        if self.static_path is not None:
            self.url_map.add(Rule(self.static_path + '/<filename>',
                                  build_only=True, endpoint='static'))
            if pkg_resources is not None:
                target = (self.package_name, 'static')
            else:
                target = os.path.join(self.root_path, 'static')
            self.wsgi_app = SharedDataMiddleware(self.wsgi_app, {
                self.static_path: target   # URL路径和实际文件目录(static文件夹)的映射
            })
        
        # Jinja2 环境,它通过jinja_options创建,加载器(loader)通过
        self.jinja_env = Environment(loader=self.create_jinja_loader(),
                                     **self.jinja_options)
        # 将url_for, get_flashed_message 作为全局对象填充入模板上下文中,可以在模板中调用它们
        self.jinja_env.globals.update(
            url_for=url_for,
            get_flashed_messages=get_flashed_messages
        )

上面就是一个 Flask 实例化时所做的工作,其实就是保存了一下配置信息,设置了一下Jinja2 环境,并定义了一个URL 映射对象,用于映射URL 到函数之间的关系。

总结

开篇主要讲了初始化一个Flask对象,内部做了什么工作,配置了一下信息,设置了一下Jinja2 环境,定义了一些视图函数存放的数据结构,定义了一个Map对象用于后面保存URL 和 视图函数的映射关系。接下来会有更多关于werkzeug, jinja2 和 WSGI 相关文章放出来,敬请期待!!!

提示

本文会同步flask 0.1版本的代码注释在github, 请通过https://github.com/guoweikuang/flask-annotate 查看整个flask 0.1 的代码注释,目前没有完成所有注释

参考

flask文档
flask项目源码0.1版本
flask注释版

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

推荐阅读更多精彩内容