0%

flask源码之类视图(九)

引言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from flask import Flask, request, current_app, jsonify
import uuid

from flask.views import MethodView

flask_app = Flask(__name__)


class HelloView(MethodView):
def get(self):
return jsonify(
code=-1,
msg="success",
data=[]
)


flask_app.add_url_rule("/", HelloView.as_view("hello"))

if __name__ == '__main__':
flask_app.run()

代码很简单,类视图的好处在于,你可以通过写基类,来处理一些公共的操作,你甚至还可以复用一些已经写好的类视图,让你少些很多代码,更多信息请参考django restframework

实现

源码在 flask.views.py

1
2
3
4
5
6
7
8
9
10
11
12
class MethodView(with_metaclass(MethodViewType, View)):

def dispatch_request(self, *args, **kwargs):
meth = getattr(self, request.method.lower(), None)

# If the request method is HEAD and we don't have a handler for it
# retry with GET.
if meth is None and request.method == "HEAD":
meth = getattr(self, "get", None)

assert meth is not None, "Unimplemented method %r" % request.method
return meth(*args, **kwargs)

这是一个用到元类来定义自己的类

1
flask_app.add_url_rule("/", HelloView.as_view("hello"))

我们知道,这种方式注册路由,最后flask大概会这样执行

1
2
obj=HelloView.as_view("hello")
rv=obj()

所以我们看看 as_view返回了什么

as_view定义在父类 View

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class View(object):

#: A list of methods this view can handle.
methods = None

#: Setting this disables or force-enables the automatic options handling.
provide_automatic_options = None

decorators = ()

@classmethod
def as_view(cls, name, *class_args, **class_kwargs):
"""Converts the class into an actual view function that can be used
with the routing system. Internally this generates a function on the
fly which will instantiate the :class:`View` on each request and call
the :meth:`dispatch_request` method on it.

The arguments passed to :meth:`as_view` are forwarded to the
constructor of the class.
"""

def view(*args, **kwargs):
self = view.view_class(*class_args, **class_kwargs)
return self.dispatch_request(*args, **kwargs)

if cls.decorators:
view.__name__ = name
view.__module__ = cls.__module__
for decorator in cls.decorators:
view = decorator(view)

# We attach the view class to the view function for two reasons:
# first of all it allows us to easily figure out what class-based
# view this thing came from, secondly it's also used for instantiating
# the view class so you can actually replace it with something else
# for testing purposes and debugging.
view.view_class = cls
view.__name__ = name
view.__doc__ = cls.__doc__
view.__module__ = cls.__module__
view.methods = cls.methods
view.provide_automatic_options = cls.provide_automatic_options
return view
def dispatch_request(self):
"""Subclasses have to override this method to implement the
actual view function code. This method is called with all
the arguments from the URL rule.
"""
raise NotImplementedError()

原理很简单,就是as_view内部定义了一个闭包,然后返回这个闭包函数,所以最后还是相当于执行了视图函数

值得注意的时,这里这里的view函数最后调用 dispatch_request方法,而这个方法在View中没有实现

所以View类不能单独使用,或者要定义 dispatch_request才能使用

MethodView定义了 dispatch_request方法

1
2
3
4
5
6
7
8
9
10
11
12
class MethodView(with_metaclass(MethodViewType, View)):

def dispatch_request(self, *args, **kwargs):
meth = getattr(self, request.method.lower(), None)

# If the request method is HEAD and we don't have a handler for it
# retry with GET.
if meth is None and request.method == "HEAD":
meth = getattr(self, "get", None)

assert meth is not None, "Unimplemented method %r" % request.method
return meth(*args, **kwargs)

他的作用很简单,就是拿到HTTP请求的方法之后,转换为小写,调用同名的类方法

例如 HTTP GET->"get"->get方法

元类

python中类也是对象,类是由元类创建的

1
class MethodView(with_metaclass(MethodViewType, View)):

MethodView是由元类 MethodViewType创建的

也就是说它不是你看到的样子,他还有一些属性或者发放是被MethodViewType在创建过程中动态添加的

所以如何给类动态添加属性,就是元类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MethodViewType(type):
"""Metaclass for :class:`MethodView` that determines what methods the view
defines.
"""

def __init__(cls, name, bases, d):
super(MethodViewType, cls).__init__(name, bases, d)

if "methods" not in d:
methods = set()

for base in bases:
if getattr(base, "methods", None):
methods.update(base.methods)

for key in http_method_funcs:
if hasattr(cls, key):
methods.add(key.upper())
if methods:
cls.methods = methods

元类的一个好处是,你可以在方法中拿到你正在创建的类的名字(name),父类(bases),属性(d)

例如我们用 MethodViewType去创建 MethodView,那这个name就是他的name

去创建 HelloView,那么name就是 这个类的名字

同时注意d是当前被创建类的属性的集合,不包括父类

这个元类的作用很简单,就是给你要创建类添加 methods属性,而这个methods属性就相当于你之前在路由中定义的methods

1
@flask_app.route("/",methods=["GET"])

也就是说,在类视图中,我们还可以不在路由中定义所接受的方法

至于为什么非要用元类,其实也不是必须要用,或许是作者觉得方便