0. 前情提要:

  • 掌握基础的flask语法知识与python语法知识。

  • 参考资料:https://www.bilibili.com/video/BV11Y411h71J

0.1 flask版本

  • 本文所学习的flask源码的版本为0.12,建议在虚拟环境中安装。

    pip install flask==0.12
    

    注意,我在研究代码并搜寻网上的源码解读文章时,发现文章中的源码和自己看到的源码是不一样的,并且我的最简单的flask程序是无法运行的,会报错cannot-import-name-markup-from-jinja2

    经查询,StackOverflow给了我答案,flask与其依赖包jinja2的版本不匹配。

    只是简单的通过pip install flask==0.12下载0.12版本的flask是不行的,其依赖的werkzeugjinja2的仍会自动下载最新版本的,故而这里需要给其相关依赖包降级。

    我在网上找到0.12版本的flask适配的依赖包版本,按照下面的版本给依赖包降级即可。据本人实操,给werkzeugJinj2itsdangerousMarkupSafe降级即可使用,且源码与网上搜到的基本保持一致了。

    降级的方法:

    Pycharm---->File---->Settings---->Project---->Python Interpreter----> + ---->搜索框中输入Werkzeug---->降级

0.2 查看源码

  • 源码跳转:按住ctrl后,单击需要查看源码的函数或类对象即可

  • 源码回退:Alt+Ctrl +方向左键

​ 或者,选中Pycharm---->View---->Appearance---->Toolbar,之后点击左上角的箭头即可

  • 源码部分会删除注释及一些不相干部分,只讲重点代码。

0.3 flask程序

本文针对下列简单的flask程序讨论源码

  • 项目目录
hello文件夹- settings- config.py        【配置文件】- static- login.html- templates- login.html- index.html- hello.py
  • flask程序
from flask import Flask, requestapp = Flask(__name__)
app.config.from_object("settings.config")@app.before_request
def f1():pass@app.before_request
def f2():pass@app.after_request
def f3(response):return response@app.after_request
def f4(response):return response@app.before_first_request
def f5():pass@app.before_first_request
def f6():pass@app.route('/hello')
def hello():return 'Hello, Flask!'if __name__ == "__main__":app.run()

1. Flask创建app对象

1.1 初识Flask对象

app = Flask(__name__)

# 1. 实例化一个Flask对象
# 2. __name__是一种魔法属性,即当前模块(py文件)的名字
#   【注:若当前模块为启动模块,则__name__ = "__main__"】
# 3. 该参数是为了指明flask的根目录,默认在该目录下的static中寻找静态资源,templates目录中寻找模板资源
#   【静态资源可通过.../static/index.html访问,此时不为url,而是静态目录】
# 4. 若输入的模块不存在,则默认在当前py文件的根目录寻找资源
# 5. 注意不要随意输入,例如abc模块时存在的

1.2 初始化app = Flask(__name__)

按住ctrl后,单击app = Flask(__name__)中的Flask,查看Flask类的初始化函数__init__

# Flask.__init__
def __init__(self, import_name, static_path=None, static_url_path=None,static_folder='static', template_folder='templates',instance_path=None, instance_relative_config=False,root_path=None):....

首先了解一下参数的含义:

常用参数

  • import_name :flask的以该模块所在目录为根目录,默认在该目录下的static中寻找静态资源,templates目录中寻找模板资源

  • static_url_path: 访问静态资源的url前缀,默认值是static。假设有静态资源apple.png,默认情况下输入127.0.0.1:5000/static/apple.png,即可访问该静态资源。

    app = Flask(__name__,static_url_path="/python")
    # 则访问127.0.0.1/python/apple.png,即可访问该静态资源
    
  • static_folder:存放静态资源的目录,默认为static。

    注意与static_url_path作区分,我们考虑这么一个问题,当访问/login.html时,我们究竟访问的是静态资源,还是已经绑定了视图函数的url呢?

    # 1.静态资源
    - hello文件夹- static文件夹-login.html    【静态资源】
    # 2.绑定了视图函数的url
    @app.route('/login.html')
    def login():return "hello login"
    

    所以flask将static_url_path作为访问静态资源的url前缀,当访问url的前缀为设定的static_url_path值时,认为访问的是静态资源。

    即默认情况下,

    访问/static/login.html,则返回静态资源login.html

    访问/login.html,则认为访问的是绑定了视图函数的url,执行其视图函数login

  • template_folder:存放模板资源的 目录,默认为templates。

其他参数

  • instance_path:目前不理解这个参数的意义,详情参考

    • Flask 实例文件夹_w3cschool
    • python - flask中,instance_path参数的存在意义是什么呢

代码分析

# Flask.__init__
def __init__(self, import_name, static_path=None, static_url_path=None,static_folder='static', template_folder='templates',instance_path=None, instance_relative_config=False,root_path=None):"""将相关参数封装到app中"""# 调用基类的初始化函数将import_name,template_folder,rootpath封装到app中_PackageBoundObject.__init__(self, import_name,template_folder=template_folder,root_path=root_path)# 将static_url_path, static_folder参数等封装到app中self.static_url_path = static_url_pathself.static_folder = static_folder..."""一些重要的实例属性"""# 加载配置文件,其详细内容在下一部分会介绍self.config = self.make_config(instance_relative_config)# 用于存放视图函数和endpoint的对应关系,endpoint为键,视图函数为值self.view_functions = {}# 用于存放before_request函数,即视图函数执行前执行的函数。内部格式为{None:[f1,f2]}self.before_request_funcs = {}# 用于存放before_first_request函数,即第一次接收请求时视图函数执行前执行的函数。内部格式为[f5,f6]self.before_first_request_funcs = []# 用于存放after_request函数,即视图函数执行后执行的函数。内部各式为{None:[f3,f4]}self.after_request_funcs = {}# 用于存放Rule对象,Rule对象内部封装了url,endpoint,methods等属性self.url_map = Map()# 是否为已经获取第一次请求的标识,默认为False值,即还未接收第一次请求。当接收过第一次请求后会设为Trueself._got_first_request = False"""加载静态资源的路由,通过这一路由可以访问静态资源"""if self.has_static_folder:self.add_url_rule(self.static_url_path + '/<path:filename>',endpoint='static',view_func=self.send_static_file)
  1. 调用_PackageBoundObject.__init__函数

    # 此时的self是Flask的实例化对象app
    def __init__(self, import_name, template_folder=None, root_path=None):self.import_name = import_nameself.template_folder = template_folderif root_path is None:# 获取该模块的目录,在该目录下的static中寻找静态资源,templates中寻找模板资源root_path = get_root_path(self.import_name)#: Where is the app root located?self.root_path = root_pathself._static_folder = Noneself._static_url_path = None
    
  2. Flask类属性

    url_rule_class = Rule
    config_class = Config
    
  3. app实例属性

    self.属性 = valueself.import_name
    self.static_url_path
    self.static_folder
    self.templates_folderself.config = make_config(instance_relative_config)
    self.view_functions = {}       # 视图函数和endpoint的对应关系
    self.error_handlers = {}
    self.before_first_request_funcs = []
    self.before_request_funcs = [] # 中间件【请求前执行的函数】
    self.after_request_funcs = []  # 中间件【请求后执行的函数】
    self.url_map = Map()           # 在其中存放url与endpoint的对应关系
    
  4. 添加静态资源的路由

    self.add_url_rule(self.static_url_path + '/<path:filename>',endpoint='static',view_func=self.send_static_file)
    # 根据当前文件的目录结构【参考前情提要】
    # url为/static/login.html,<path:filename>是转换器
    # endpoint为'static'
    # 执行视图函数:app.send_static_file
    

2. 加载配置文件

from flask import Flask
app = Flask(__name__)
app.config.from_object("settings.config")

app.config属性

在上面提及的__init__函数中初始化了app.config值,我们来看一看

# Flask.__init__函数
self.config = self.make_config(instance_relative_config)

目光转到make_config函数

def make_config(self, instance_relative=False):# 省略了对root_path的一些处理,root_path一般是app的根目录root_path = self.root_pathreturn self.config_class(root_path, self.default_config)

Flask类中定义了

# Flask
config_class = Config

make_config函数创建并返回了一个Config类对象。self.config是一个Config类。下面来看Config

Config

class Config(dict):# 初始化函数def __init__(self, root_path, defaults=None):# 调用字典类型的初始化函数,将其设为空字典{}或指定的值defaultdict.__init__(self, defaults or {})# 记录app的根目录self.root_path = root_path# 读取配置文件def from_object(self, obj):# string_types = (str, unicode)# 如果obj是str或unicode类型,则对其做一些处理,使其变为路径格式if isinstance(obj, string_types):# 对"setting.config"字符串做一些处理,得到模块的路径与名字,从而导入模块文件obj = import_string(obj)# dir():返回模块的属性列表for key in dir(obj):# 如果该属性变量名是大写的,将其属性变量名作为key,其属性值作为value,存入Config字典if key.isupper():# 执行__setattr__方法# 继承了dict的__setattr__方法self[key] = getattr(obj, key)

简单来说就是,Config是一个字典。app.config.from_object("settings.config")将配置文件settings.config中的信息,以键值对的形式存入app.config中。

3. 特殊装饰器

@app.before_request
def f1():pass@app.before_request
def f2():pass@app.after_request
def f3(response):return response@app.after_request
def f4(response):return response@app.before_first_request
def f5():pass@app.before_first_request
def f6():pass

app.before_request函数

def before_request(self, f):# 将该函数加入到app.before_request_funcs字典中# app.before_request_funcs={None:[f1,f2]}self.before_request_funcs.setdefault(None, []).append(f)return f

app.after_request函数

def after_request(self, f):# 将该函数加入到app.after_request_funcs字典中# app.after_request_funcs={None:[f3,f4]}self.after_request_funcs.setdefault(None, []).append(f)return f

app.before_first_request函数

def before_first_request(self, f):# 将该函数加入到app.before_first_request_funcs列表中# app.before_request_funcs=[f5,f6]self.before_first_request_funcs.append(f)return f

4. 配置路由

@app.route('/hello')
def hello():return 'Hello, Flask!'

app.route函数

def route(self, rule, **options):# **关键词参数接收为options【字典dict类型】# rule为"/hello"字符串def decorator(f):endpoint = options.pop('endpoint', None)self.add_url_rule(rule, endpoint, f, **options)return freturn decorator
  • 首先传入参数rule='/hello',定义了decorator函数,并返回该函数。

  • 接着,将decorator函数作为hello函数的装饰器。即执行decorator函数,参数为hello函数

    def decorator(f): endpoint = options.pop('endpoint', None)self.add_url_rule(rule, endpoint, f, **options)return f# hello_world函数名作为参数传入f
    # 执行dict类型的pop函数,pop(key[,default]),获取并获取删除中字典中的key对应元素,若关键字不存在,则返回设定的默认值default
    # 未传入关键字为endpoint的参数,故endpoint为默认值None
    
  • 执行add_url_rule函数,看下面详解

add_url_rule函数

我将它分为以下几个部分

  • 传入参数:rule='/hello'endpoint=Nonef=hello

  • 处理参数endpoint

    """endpoint处理"""#如果endpoint为空,获取当前视图函数的名字,设为endpoint,将其放入options字典中#即options={'endpoint': hello}if endpoint is None: # _endpoint_from_view_func函数逻辑:要求视图函数view_func非空,并返回它的名字endpoint = _endpoint_from_view_func(view_func)options['endpoint'] = endpoint
    
  • 处理参数methods

    小知识点补充:

    # a = b or c ,将b和c中不为None的值赋给a,如果b和c均不为None,则将前一个b赋值给a
    

    代码:

    """methods处理"""# 将method方法从options字典中取出并删除,若不存在设为Nonemethods = options.pop('methods', None)# 如果methods参数为空,获取view_func的methods属性,若其不存在,设为默认值'GET'if methods is None:methods = getattr(view_func, 'methods', None) or ('GET',)# methods参数要求是可迭代对象,一般是列表['GET','POST',...]if isinstance(methods, string_types):raise TypeError('Allowed methods have to be iterables of strings, ''for example: @app.route(..., methods=["POST"])')# methods中的参数要求大写methods = set(item.upper() for item in methods)# 其他处理略
    
  • 路由封装

    """将url和endpoint的对应关系封装到Rule对象,再将Rule对象封装到Map对象"""# rule = Rule(url, methods, endpoint)rule = self.url_rule_class(rule, methods=methods, **options)# 把rule对象封装到app.url_map中self.url_map.add(rule)"""绑定endpoint与视图函数"""# app.view_functions = {endpoint:view_func}# 即以endpoint为key,视图函数view_func为value存入app.view_functions字典中"""if view_func is not None:# 从view_functions的中获取endpoint对应的视图函数old_func = self.view_functions.get(endpoint)# 如果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)# 否则,以endpoint为key,视图函数view_func为value存入app.view_functions字典中self.view_functions[endpoint] = view_func
    

5. 总体流程图

Flask源码学习【个人自学记录】-应用准备阶段相关推荐

  1. flask源码学习-路由的注册与请求处理的过程

    Flask源码分析 本文环境python3.5.2,flask-1.0.2. Flask的路由注册 此时编写的脚本内容如下, from flask import Flaskapp = Flask(__ ...

  2. flask源码学习-helloworld与本地启动流程

    Flask源码分析 本文环境python3.5.2,flask-1.0.2. Flask的初探 首先,在项目文件夹下建立flask_run.py文件,然后写入如下, from flask import ...

  3. AVB源码学习(二):Uboot阶段AVB2.0校验流程

    参考资料 感谢前辈的blog,安全相关的资料可太少了,很详细很卓越 https://blog.csdn.net/jackone12347/article/details/116241676 (我是真的 ...

  4. python flask源码解析_用尽洪荒之力学习Flask源码

    [TOC] 一直想做源码阅读这件事,总感觉难度太高时间太少,可望不可见.最近正好时间充裕,决定试试做一下,并记录一下学习心得. 首先说明一下,本文研究的Flask版本是0.12. 首先做个小示例,在p ...

  5. 【博学谷学习记录】超强总结,用心分享 | 架构师 Mybatis源码学习总结

    Mybatis源码学习 文章目录 Mybatis源码学习 一.Mybatis架构设计 二.源码剖析 1.如何解析的全局配置文件 解析配置文件源码流程 2.如何解析的映射配置文件 Select inse ...

  6. Spark-Core源码学习记录 3 SparkContext、SchedulerBackend、TaskScheduler初始化及应用的注册流程

    Spark-Core源码学习记录 该系列作为Spark源码回顾学习的记录,旨在捋清Spark分发程序运行的机制和流程,对部分关键源码进行追踪,争取做到知其所以然,对枝节部分源码仅进行文字说明,不深入下 ...

  7. jQuery源码学习之Callbacks

    jQuery源码学习之Callbacks jQuery的ajax.deferred通过回调实现异步,其实现核心是Callbacks. 使用方法 使用首先要先新建一个实例对象.创建时可以传入参数flag ...

  8. Vuex源码学习(五)加工后的module

    没有看过moduleCollection那可不行!Vuex源码学习(四)module与moduleCollection 感谢提出代码块和截图建议的小伙伴 代码块和截图的区别: 代码块部分希望大家按照我 ...

  9. 以太坊源码学习 -- EVM

    以太坊源码学习 – EVM 学习文档链接:here 一.虚拟机外 主要功能: 执行前将Transaction类型转化成Message,创建虚拟机(EVM)对象,计算一些Gas消耗,以及执行交易完毕后创 ...

最新文章

  1. 李德毅院士《探索新一代人工智能产业发展》
  2. Cisco路由器的口令恢復
  3. js 中的五种继承方法
  4. vue-cli3+typescript+路由懒加载报错问题
  5. android q桌面,Android Q带来全新桌面模式
  6. what?传统风控策略,无法cover到以下风控场景
  7. python len函数_你需要了解的最重要的Python概念
  8. tasm报错illegal memory reference的解决办法
  9. [Remoting]在.NET環境實作Flex 3 Remoting - (2) Flex Builder 環境設定
  10. Ubuntu18.04 修改IP地址、查看网关、防火墙
  11. PS自定义形状+笔刷添加打造完美水印
  12. 项目管理体验营day3:项目管理之沟通技巧
  13. 【艾特淘】直通车数据化选款技巧
  14. 计算机产品校园营销方案,惠普笔记本电脑校园营销策划方案.doc
  15. 遇到的算法题--02(斗牛)
  16. php theexcerpt,WordPress获取文章摘要函数the_excerpt详解
  17. java RSA生成公钥对象和私钥对象
  18. imx6ull 以太网
  19. 基于亚马逊云科技无服务器服务快速搭建电商平台——部署篇
  20. 2 年前端面试字节跳动、YY、虎牙、BIGO心路历程总结

热门文章

  1. Java中将一个对象赋给另一个对象时会发生什么?
  2. 超级干货 :一文读懂大数据计算框架与平台(升级版)
  3. 在win2008R2上使用(NLB)网络负载均衡
  4. centos7配置yum为国内源
  5. Python绘制训练过程的loss和accuracy曲线
  6. Docker 挂载方式启动 Nginx
  7. 花之语第一期:山茶花
  8. 为微信开发填坑:微信网页支付的开发流程及填坑技巧 1
  9. 使用Node.JS进行谷歌地图定位
  10. 团队管理课程培训心得(三)