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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 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前缀等。


目录
相关文章
|
2月前
|
Python
用python进行视频剪辑源码
这篇文章提供了一个使用Python进行视频剪辑的源码示例,通过结合moviepy和pydub库来实现视频的区间切割和音频合并。
61 2
|
9天前
|
开发框架 前端开发 JavaScript
利用Python和Flask构建轻量级Web应用的实战指南
利用Python和Flask构建轻量级Web应用的实战指南
27 2
|
18天前
|
JSON API 数据格式
如何使用Python和Flask构建一个简单的RESTful API。Flask是一个轻量级的Web框架
本文介绍了如何使用Python和Flask构建一个简单的RESTful API。Flask是一个轻量级的Web框架,适合小型项目和微服务。文章从环境准备、创建基本Flask应用、定义资源和路由、请求和响应处理、错误处理等方面进行了详细说明,并提供了示例代码。通过这些步骤,读者可以快速上手构建自己的RESTful API。
25 2
|
20天前
|
JSON API 数据格式
构建RESTful APIs:使用Python和Flask
构建RESTful APIs:使用Python和Flask
27 1
|
1月前
|
JSON API 数据格式
构建RESTful APIs:使用Python和Flask
【10月更文挑战第12天】本文介绍了如何使用Python和Flask构建一个简单的RESTful API。首先概述了API的重要性及RESTful API的基本概念,接着详细讲解了Flask框架的特性和安装方法。通过创建一个基本的Flask应用,定义了处理“图书”资源的GET、POST、PUT和DELETE方法的路由,展示了如何处理请求和响应,以及如何进行错误处理。最后,提供了运行和测试API的方法,总结了Flask在构建RESTful API方面的优势。
32 1
|
1月前
|
JSON API 数据格式
构建RESTful APIs:使用Python和Flask
【10月更文挑战第10天】本文介绍了如何使用Python和Flask构建一个简单的RESTful API。Flask是一个轻量级的Web应用框架,适合小型项目和微服务。文章从环境准备、创建基本Flask应用、定义资源和路由、请求和响应处理、错误处理等方面进行了详细说明,并提供了代码示例。通过这些步骤,读者可以快速掌握使用Flask构建RESTful API的方法。
51 1
|
1月前
|
数据库 开发者 Python
使用Python和Flask构建Web应用
【10月更文挑战第2天】使用Python和Flask构建Web应用
26 2
|
1月前
|
API 数据库 开发者
Flask:Python的轻量级Web框架
Flask:Python的轻量级Web框架
34 2
|
1月前
|
自然语言处理 Java 编译器
为什么要看 Python 源码?它的结构长什么样子?
为什么要看 Python 源码?它的结构长什么样子?
26 2
|
1月前
|
Python
源码解密 Python 的 Event
源码解密 Python 的 Event
40 1