Flask中route装饰器的使用

在Flask构建的项目中我们通常会有如下代码:

@api.route('/register', methods=['POST'])
def create_client():
    form = ClientForm().validate_for_api()
    promise = {
        ClientTypeEnum.USER_EMAIL: __register_user_by_email
    }
    promise[form.type.data]()

    return Success()

对于熟悉python的朋友们来说,很明显这是一个装饰器, 但是我们在刚接触python的时候的装饰器都是长下面这样的:

def func(n):
    print('before f')
    def f():
        print('do Any')
        n()
        print('after do any')
    print('end f')
    return f

@func
def hello():
    print('hello')

可以看到在装饰器上面是没有带参数的,那么Flask是怎么处理的呢?

# 这是flask源码的装饰器实现
# rule即是定义的url规则, 例如'/register'
# options是其他的附加的关键字参数, 例如 methods=['GET']
def route(self, rule, **options):

    # f即是被修饰的函数
    def decorator(f):
        # 他会去从关键字参数中寻找endpoint, 然后加上url规则,和被修饰函数一起调用add_url_rule方法
        endpoint = options.pop('endpoint', None)
        self.add_url_rule(rule, endpoint, f, **options)
        return f
    # 这里是把装饰器返回去, 所以@app.route(), 实际上是@decorator, 也就是一个装饰器
    return decorator

通过上面的注释可以知道, 在使用装饰器的时候,是先通过route方法进行调用, 然后返回的一个装饰器, 所以修饰函数的实际上还是一个@decorator

举个简单的例子, 上面的func修饰器如果去除编译器的特性, 实际上的代码就是

def func(n):
    print('before f')
    def f():
        print('do Any')
        n()
        print('after do any')
    print('end f')
    return f

@func
def hello():
    print('hello')

# 上面是编译器优化的代码, 下面是其实际运行的代码
hello = func(hello)

#可以看到,使用装饰器的实质是使用一个与函数同名的变量接收一个该函数作为参数调用装饰器后的返回值
该返回值也是一个函数, 而装饰器在执行过程中可以附加代码, 这就是装饰器的意义。

那么对于我们来说flask里面的装饰器原理就只有这么简单么?其实不是的!

在我们构建Flask项目的时候我们一般会采取蓝图(Blueprint)来进行项目功能划分分层, 而有的甚至会采用红图(Redprint)进行

视图划分分层,而不管是蓝图还是红图, 我们都需要让他们挂载到我们的核心对象app上, 也就是项目中构建的Flask()上,一般采取

的措施都是蓝图挂载到app上,视图函数挂载到蓝图上, 或者蓝图挂载到app上, 红图挂载到蓝图上, 前后者之分在于项目架构分层

的不同, 后者是认为蓝图应该是模块级别的划分, 而不是视图函数级别的划分, 所以利用红图挂载视图函数, 利用蓝图挂载每个包

内的红图。

那么上面写了这么大一长串和装饰器有什么关系呢?其实是有关系的, 在挂载的时候接触到了装饰器

Flask中route装饰器的使用

上面是文件结构

首先是在主文件里调用创建引用的方法:

# app包内的app.py

def register_blueprints(app):
    from app.api.v1 import create_blueprint_v1
    app.register_blueprint(create_blueprint_v1(), url_prefix='/v1')

def create_app():
    app = Flask(__name__)
    app.config.from_object('app.config.settings')
    app.config.from_object('app.config.secure')

    register_blueprints(app)
    register_plugin(app)

    return app
# v1的__init__模块

from app.api.v1 import user, book, client

def create_blueprint_v1():
    bp_v1 = Blueprint('v1', __name__)

    user.api.register(bp_v1)
    book.api.register(bp_v1)
    client.api.register(bp_v1)
    return bp_v1

下面以user为例, 贴上user的代码:

# v1内的user模块

from app.libs.redprint import RedPrint

api = RedPrint('user')

@api.route('/get')
def get_user():
    return jsonify({'name': 'will'})

下面再来看看红图

class RedPrint:
    def __init__(self, name):
        self.name = name
        self.mound = []

    def route(self, rule, **options):
        def decorator(f):
            self.mound.append((f, rule, options))
            return f
        return decorator

    def register(self, bp, url_prefix=None):
        if url_prefix is None:
            url_prefix = '/' + self.name
        for f, rule, options in self.mound:
            endpoint = options.pop('endpoint', f.__name__)
            bp.add_url_rule(url_prefix + rule, endpoint, f, **options)

可以看到红图是长这样的,仿照蓝图实现了route和register方法

那么上面说这么多废话贴这么多BUG为了说明什么呢?其实我想说的时,在注册的时候红图对象回去它的内部找mound属性, 而

mound属性是在作为route装饰器执行完成后才被填充的, 那么整个过程中并没有请求进来, 也就是在注册的时候被装饰的函数并没

有执行, 那么mound是怎么被填充的?

答案是在导入的时候装饰器就已经执行了, 我们从模块内部导入某个变量或者函数的时候其内部的装饰器就已经装载完毕!可以看

看下面的栗子:

# 自己写的很丑的demo.py
import time

def cover(s, **options):
    print('hello')
    def decorator(f):
        return f
    return decorator

@cover('/', methods=['will'])
def doAny(n):
    print('do any')
    time.sleep(n)



def world():
    print('world')

然后在另外一个文件里导入上面文件的world函数

# 很简洁的模块test.py
from demo import world

是不是很简单, 那么如果我们运行test.py会发生什么呢?答案是他会打印hello...

是不是很神奇?其实这里面牵涉到了导包的姿势, 如 import和from...import的区别, 有兴趣的可以去看看其他大佬的优异博客。

谢谢观赏!