Flask 源码阅读-下篇 |Python 主题月

简介: flask项目大名鼎鼎,不需要多做介绍。我把它称之为python服务开发的TOP2项目,另外一个就是django。这两个项目各有千秋,各自有不同的应用场景,都需要深入理解,熟练掌握。本次源码选择的版本是 1.1.2,我会采用慢读法,尽自己最大努力把它讲透。

flask项目大名鼎鼎,不需要多做介绍。我把它称之为python服务开发的TOP2项目,另外一个就是django。这两个项目各有千秋,各自有不同的应用场景,都需要深入理解,熟练掌握。本次源码选择的版本是 1.1.2,我会采用慢读法,尽自己最大努力把它讲透。本篇是第3篇-自助餐,主要包括:


  • view 解析
  • blueprint 解析
  • 小结


view 解析



flask一个简单的监听函数如下:


@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        return do_the_login()
    else:
        return show_the_login_form()
复制代码


如果URL多了,就需要实现多个监听函数,代码会比较乱。同时对于一个URL,在监听函数中区分http的method,进行不同的业务逻辑处理。一个函数中处理两种逻辑,也不太符合单一职责,会让代码难以维护。


这种情况下,就需要使用视图。下面是一个视图示例:


class CounterAPI(MethodView):
    def get(self):
        return session.get('counter', 0)
    def post(self):
        session['counter'] = session.get('counter', 0) + 1
        return 'OK'
app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
复制代码


CounterAPI可以把一个类的实例方法注册到一个URL上,自动将get和post方法区分开。我们一起看看View的实现,先是所有View的基类:


class View(object):
    @classmethod
    def as_view(cls, name, *class_args, **class_kwargs):
        def view(*args, **kwargs):
            self = view.view_class(*class_args, **class_kwargs)
            return self.dispatch_request(*args, **kwargs)
        ...
        view.view_class = cls
        view.__name__ = name
        ...
        return view
复制代码


as_view函数返回一个视图函数,在视图函数里可以派发和处理request。


MethodViewType是一个元类,定义了视图支持的所有HTTP方法的集合:


http_method_funcs = frozenset(
    ["get", "post", "head", "options", "delete", "put", "trace", "patch"]
)
class MethodViewType(type):
    def __init__(cls, name, bases, d):
        super(MethodViewType, cls).__init__(name, bases, d)
        if "methods" not in d:
            methods = set()
            ...
            for key in http_method_funcs:
                if hasattr(cls, key):
                    methods.add(key.upper())
            if methods:
                cls.methods = methods
复制代码


MethodView是使用MethodViewType和View创建的新类:


class MethodView(with_metaclass(MethodViewType, View)):
    def dispatch_request(self, *args, **kwargs):
        meth = getattr(self, request.method.lower(), None)
        ...
        return meth(*args, **kwargs)
复制代码


with_metaclass 是为了兼容python2的语法,可以简单的理解为继承自MethodViewType和View


dispatch_request中根据请求的http-method找到对应的方法,进行执行。


view的处理函数还可以增加装饰器,示例如下:


# 使用示例
class SecretView(View):
    methods = ['GET']
    decorators = [login_required]
class View(object):
    @classmethod
    def as_view(cls, name, *class_args, **class_kwargs):
        ...
        if cls.decorators:
            view.__name__ = name
            view.__module__ = cls.__module__
            # 包装上装饰器
            for decorator in cls.decorators:
                view = decorator(view)
        ...
        return view
# 装饰器
def login_required(view):
    @functools.wraps(view)
    def wrapped_view(**kwargs):
        if g.user is None:
            return redirect(url_for('auth.login'))
        return view(**kwargs)
    return wrapped_view
复制代码


blueprint 解析



View相对还是比较单薄,大型的项目都会分模块进行开发,所以flask还有blueprint的概念。下面是示例项目flaskr中的 auth.py :


import functools
from flask import (
    Blueprint, flash, g, redirect, render_template, request, session, url_for
)
...
bp = Blueprint('auth', __name__, url_prefix='/auth')
@bp.route('/register', methods=('GET', 'POST'))
def register():
    ...
@bp.route('/login', methods=('GET', 'POST'))
def login():
    ...
@bp.route('/logout')
def logout():
    ...
复制代码


这里定义了一个名称叫做auth的蓝图,里面定义了3个方法: register , login 和 logout 。蓝图在app的__init__.py中注册:


def create_app():
    app = ...
    # existing code omitted
    from . import auth
    app.register_blueprint(auth.bp)
    return app
复制代码


在flask项目中还有名为blog的蓝图,提供博客文章的增删改查方法:


bp = Blueprint("blog", __name__)
@bp.route("/")
def index():
    ...
@bp.route("/create", methods=("GET", "POST"))
@login_required
def create():
    ...
复制代码


采用这种方式,就可以很方便的分模块进行程序开发。


了解了bluerpint的使用方法后,我们再看看其实现原理。


class Blueprint(_PackageBoundObject):
    def __init__(
        self,
        name,
        import_name,
        static_folder=None,
        static_url_path=None,
        template_folder=None,
        url_prefix=None,
        subdomain=None,
        url_defaults=None,
        root_path=None,
        cli_group=_sentinel,
    ):
        _PackageBoundObject.__init__(
            self, import_name, template_folder, root_path=root_path
        )
        self.name = name
        self.url_prefix = url_prefix
        self.subdomain = subdomain
        self.static_folder = static_folder
        self.static_url_path = static_url_path
        self.deferred_functions = []
        if url_defaults is None:
            url_defaults = {}
        self.url_values_defaults = url_defaults
        self.cli_group = cli_group
复制代码


上面Blueprint的构造函数中显示:


  • 继承自_PackageBoundObject。_PackageBoundObject上一篇介绍过,主要实现本地目录的动态加载,因为蓝图也有一些模版需求,所以继承了_PackageBoundObject。
  • deferred_functions数组是蓝图的所有视图的集合
  • url_prefix,subdomain, static_folder等是蓝图模块化的功能参数


蓝图的route装饰器:


def route(self, rule, **options):
    def decorator(f):
        endpoint = options.pop("endpoint", f.__name__)
        self.add_url_rule(rule, endpoint, f, **options)
        return f
    return decorator
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
    ...
    self.record(lambda s: s.add_url_rule(rule, endpoint, view_func, **options))
def record(self, func):
    self.deferred_functions.append(func)
复制代码


这里主要的疑问在添加视图函数时候的lambda函数的参数 s 是什么?继续看看蓝图的注册:


# app的方法
def register_blueprint(self, blueprint, **options):
    self.blueprints[blueprint.name] = blueprint
    self._blueprint_order.append(blueprint)
    first_registration = True
    blueprint.register(self, options, first_registration)
# blueprint的方法
def register(self, app, options, first_registration=False):
    self._got_registered_once = True
    state = self.make_setup_state(app, options, first_registration)
    for deferred in self.deferred_functions:
        deferred(state)
   ...
复制代码


make_setup_stat创建BlueprintSetupState对象, 然后执行蓝图route添加到deferred_functions的方法。这个方法就是前面的lambda函数,前面的 s 就是state对象.


class BlueprintSetupState(object):
    def __init__(self, blueprint, app, options, first_registration):
        #: a reference to the current application
        self.app = app
        self.blueprint = blueprint
    def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
        """A helper method to register a rule (and optionally a view function)
        to the application.  The endpoint is automatically prefixed with the
        blueprint's name.
        """
        if self.url_prefix is not None:
            if rule:
                rule = "/".join((self.url_prefix.rstrip("/"), rule.lstrip("/")))
            else:
                rule = self.url_prefix
        options.setdefault("subdomain", self.subdomain)
        if endpoint is None:
            endpoint = _endpoint_from_view_func(view_func)
        defaults = self.url_defaults
        if "defaults" in options:
            defaults = dict(defaults, **options.pop("defaults"))
        self.app.add_url_rule(
            rule,
            "%s.%s" % (self.blueprint.name, endpoint),
            view_func,
            defaults=defaults,
            **options
        )
复制代码


BlueprintSetupState中建立了app和blueprint的关联,并且使用app的add_url_rule方法,把blueprint的视图函数注册进入app。


小结



flask是一个 micro 框架,但是也(至少)可以支持中型项目。我们可以利用Blueprint和View功能进行模块化: View可以很好的区分URL上的http-method;Blueprint可以很好的定义子域名和URL前缀等。


目录
相关文章
|
22天前
|
人工智能 机器人 测试技术
【python】python小游戏——开心消消乐(源码)【独一无二】
【python】python小游戏——开心消消乐(源码)【独一无二】
|
22天前
|
存储 人工智能 搜索推荐
【python】python用户管理系统[简易版](源码+报告)【独一无二】
【python】python用户管理系统[简易版](源码+报告)【独一无二】
|
22天前
|
存储 数据挖掘 数据库
【Python】python天气数据抓取与数据分析(源码+论文)【独一无二】
【Python】python天气数据抓取与数据分析(源码+论文)【独一无二】
|
16天前
|
安全 数据库 C++
Python Web框架比较:Django vs Flask vs Pyramid
【4月更文挑战第9天】本文对比了Python三大Web框架Django、Flask和Pyramid。Django功能全面,适合快速开发,但学习曲线较陡;Flask轻量灵活,易于入门,但默认配置简单,需自行添加功能;Pyramid兼顾灵活性和可扩展性,适合不同规模项目,但社区及资源相对较少。选择框架应考虑项目需求和开发者偏好。
|
4天前
|
API 数据库 数据安全/隐私保护
Flask框架在Python面试中的应用与实战
【4月更文挑战第18天】Django REST framework (DRF) 是用于构建Web API的强力工具,尤其适合Django应用。本文深入讨论DRF面试常见问题,包括视图、序列化、路由、权限控制、分页过滤排序及错误处理。同时,强调了易错点如序列化器验证、权限认证配置、API版本管理、性能优化和响应格式统一,并提供实战代码示例。了解这些知识点有助于在Python面试中展现优秀的Web服务开发能力。
22 1
|
4天前
|
SQL 中间件 API
Flask框架在Python面试中的应用与实战
【4月更文挑战第18天】**Flask是Python的轻量级Web框架,以其简洁API和强大扩展性受欢迎。本文深入探讨了面试中关于Flask的常见问题,包括路由、Jinja2模板、数据库操作、中间件和错误处理。同时,提到了易错点,如路由冲突、模板安全、SQL注入,以及请求上下文管理。通过实例代码展示了如何创建和管理数据库、使用表单以及处理请求。掌握这些知识将有助于在面试中展现Flask技能。**
12 1
Flask框架在Python面试中的应用与实战
|
5天前
|
数据安全/隐私保护 Python
Python Flask-Mail实现邮件发送
Python Flask-Mail实现邮件发送
|
10天前
|
数据库 开发者 Python
Python中使用Flask构建简单Web应用的例子
【4月更文挑战第15天】Flask是一个轻量级的Python Web框架,它允许开发者快速搭建Web应用,同时保持代码的简洁和清晰。下面,我们将通过一个简单的例子来展示如何在Python中使用Flask创建一个基本的Web应用。
|
10天前
|
前端开发 数据挖掘 API
使用Python中的Flask框架进行Web应用开发
【4月更文挑战第15天】在Python的Web开发领域,Flask是一个备受欢迎的轻量级Web框架。它简洁、灵活且易于扩展,使得开发者能够快速地构建出高质量的Web应用。本文将深入探讨Flask框架的核心特性、使用方法以及在实际开发中的应用。
|
17天前
|
存储 定位技术 数据库
【python毕业设计】python基于Pygame的吃金币游戏设计与实现(源码+毕业论文)【独一无二】
【python毕业设计】python基于Pygame的吃金币游戏设计与实现(源码+毕业论文)【独一无二】