Flask源码学习【个人自学记录】-应用准备阶段
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
是不行的,其依赖的werkzeug
和jinja2
的仍会自动下载最新版本的,故而这里需要给其相关依赖包降级。我在网上找到0.12版本的
flask
适配的依赖包版本,按照下面的版本给依赖包降级即可。据本人实操,给werkzeug
,Jinj2
,itsdangerous
,MarkupSafe
降级即可使用,且源码与网上搜到的基本保持一致了。降级的方法:
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)
调用
_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
Flask类属性
url_rule_class = Rule config_class = Config
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的对应关系
添加静态资源的路由
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=None
,f=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源码学习【个人自学记录】-应用准备阶段相关推荐
- flask源码学习-路由的注册与请求处理的过程
Flask源码分析 本文环境python3.5.2,flask-1.0.2. Flask的路由注册 此时编写的脚本内容如下, from flask import Flaskapp = Flask(__ ...
- flask源码学习-helloworld与本地启动流程
Flask源码分析 本文环境python3.5.2,flask-1.0.2. Flask的初探 首先,在项目文件夹下建立flask_run.py文件,然后写入如下, from flask import ...
- AVB源码学习(二):Uboot阶段AVB2.0校验流程
参考资料 感谢前辈的blog,安全相关的资料可太少了,很详细很卓越 https://blog.csdn.net/jackone12347/article/details/116241676 (我是真的 ...
- python flask源码解析_用尽洪荒之力学习Flask源码
[TOC] 一直想做源码阅读这件事,总感觉难度太高时间太少,可望不可见.最近正好时间充裕,决定试试做一下,并记录一下学习心得. 首先说明一下,本文研究的Flask版本是0.12. 首先做个小示例,在p ...
- 【博学谷学习记录】超强总结,用心分享 | 架构师 Mybatis源码学习总结
Mybatis源码学习 文章目录 Mybatis源码学习 一.Mybatis架构设计 二.源码剖析 1.如何解析的全局配置文件 解析配置文件源码流程 2.如何解析的映射配置文件 Select inse ...
- Spark-Core源码学习记录 3 SparkContext、SchedulerBackend、TaskScheduler初始化及应用的注册流程
Spark-Core源码学习记录 该系列作为Spark源码回顾学习的记录,旨在捋清Spark分发程序运行的机制和流程,对部分关键源码进行追踪,争取做到知其所以然,对枝节部分源码仅进行文字说明,不深入下 ...
- jQuery源码学习之Callbacks
jQuery源码学习之Callbacks jQuery的ajax.deferred通过回调实现异步,其实现核心是Callbacks. 使用方法 使用首先要先新建一个实例对象.创建时可以传入参数flag ...
- Vuex源码学习(五)加工后的module
没有看过moduleCollection那可不行!Vuex源码学习(四)module与moduleCollection 感谢提出代码块和截图建议的小伙伴 代码块和截图的区别: 代码块部分希望大家按照我 ...
- 以太坊源码学习 -- EVM
以太坊源码学习 – EVM 学习文档链接:here 一.虚拟机外 主要功能: 执行前将Transaction类型转化成Message,创建虚拟机(EVM)对象,计算一些Gas消耗,以及执行交易完毕后创 ...
最新文章
- 李德毅院士《探索新一代人工智能产业发展》
- Cisco路由器的口令恢復
- js 中的五种继承方法
- vue-cli3+typescript+路由懒加载报错问题
- android q桌面,Android Q带来全新桌面模式
- what?传统风控策略,无法cover到以下风控场景
- python len函数_你需要了解的最重要的Python概念
- tasm报错illegal memory reference的解决办法
- [Remoting]在.NET環境實作Flex 3 Remoting - (2) Flex Builder 環境設定
- Ubuntu18.04 修改IP地址、查看网关、防火墙
- PS自定义形状+笔刷添加打造完美水印
- 项目管理体验营day3:项目管理之沟通技巧
- 【艾特淘】直通车数据化选款技巧
- 计算机产品校园营销方案,惠普笔记本电脑校园营销策划方案.doc
- 遇到的算法题--02(斗牛)
- php theexcerpt,WordPress获取文章摘要函数the_excerpt详解
- java RSA生成公钥对象和私钥对象
- imx6ull 以太网
- 基于亚马逊云科技无服务器服务快速搭建电商平台——部署篇
- 2 年前端面试字节跳动、YY、虎牙、BIGO心路历程总结