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是不行的,其依赖的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, request

app = 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。

其他参数
代码分析
# 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_path
    self.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值,即还未接收第一次请求。当接收过第一次请求后会设为True
    self._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_name
    
        self.template_folder = template_folder
    
        if root_path is None:
            # 获取该模块的目录,在该目录下的static中寻找静态资源,templates中寻找模板资源
            root_path = get_root_path(self.import_name)
    
        #: Where is the app root located?
        self.root_path = root_path
    
        self._static_folder = None
        self._static_url_path = None
    
  2. Flask类属性

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

    self.属性 = value
    
    self.import_name
    self.static_url_path
    self.static_folder
    self.templates_folder
    
    self.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_path
    return 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):
        # 调用字典类型的初始化函数,将其设为空字典{}或指定的值default
        dict.__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 f
    return 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字典中取出并删除,若不存在设为None
        methods = 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. 总体流程图

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值