Django源码剖析(04)--Form

表单验证在web开发中, 同样属于浓墨重彩的一笔, 许多的问题都可以在表单验证这一层将其阻隔并处理, 使得服务器资源能够高效的使用.

1. metaclass

在详细的阅读Form的源码之前, metaclass是怎么都绕不过去的知识点.而在各大论坛中, 对于元类解读较为清晰和详细的文章, 并不是很多. 本人参考StackOverflow文章:

https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python/6581949#6581949

每一个回答都值得去读一读.不过, 这里仅对票数最高的回答进行学习.


1.1 类也是对象

在Python中, 我们知道其实就是一段代码用于生产对象的.

1
2
3
4
5
6
class Person:
pass

person = Person()
print(person)
<__main__.Person instance at 0x7f0f49e77a28>

但是, Python中的类作用并不止于此, 类也是对象.
我们可以将类这个对象赋值给一个变量, 可以进行copy, 可以动态的添加一些属性, 可以传递给函数作为参数.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
In [3]: def foo(object):
...: print(object)

In [4]: foo(Person)
__main__.Person

In [6]: print(hasattr(Person, "new_attribute"))
False

In [7]: Person.new_attribute = "foo"

In [8]: print(hasattr(Person, "new_attribute"))
True

In [9]: print(Person.new_attribute)
foo

1.2 动态创建类

既然类也是一种对象的话, 那么我们就可以有选择的创建了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
In [10]: def choose_class(name):
...: if name == "foo":
...: class Foo:
...: pass
...: return Foo
...: else:
...: class Bar:
...: pass
...: return Bar
...:

In [11]: foo_class = choose_class("foo")

In [12]: foo_class()
Out[12]: <__main__.Foo instance at 0x7fb3977d1e18>

In [13]: bar_class = choose_class("other")

In [14]: bar_class()
Out[14]: <__main__.Bar instance at 0x7fb39c2b21b8>

这样的方式仍然不是很动态, 因为你需要编写整个类的代码.
type除了能够判断一个对象是什么类型的以外, 还能够接收一个类作为参数, 并返回一个类.

1
type(类的名称, 父类元组, 属性key以及value所组成的字典)

那么在上面的Person类就可以通过type函数来创建:

1
2
3
4
PersonClass = type("PersonClass", (), {})  # 没有父类, 所以为一个空tuple; 属性也没有, 所以是一个空字典

In [6]: print(PersonClass)
<class '__main__.PersonClass'>

1
2
3
4
5
6
7
8
class Foo:
bar = True

In [7]: FooClass = type("Foo", (), {"bar": True})
...:

In [9]: FooClass.bar
Out[9]: True

通过type创建的类的属性为类属性, 需要注意一下.
所以说, 在我们使用class关键字来创建类的时候, 其实Python是通过type这种方式来进行创建的.

1.3 什么是元类?

元类其实就是创建类的原材料.str是一个创建string对象的类, int是创建integer对象的类, 那么type, 就是创建class对象的类.
我们可以使用__class__属性来验证这一点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
In [20]: age = 10

In [21]: age.__class__
Out[21]: int

In [22]: name = "samrt"

In [23]: name.__class__
Out[23]: str

In [24]: def foo(): pass

In [25]: foo.__class__
Out[25]: function

那么int, str, function__class__属性是什么呢?

1
2
3
4
5
6
7
8
In [27]: age.__class__.__class__
Out[27]: type

In [28]: name.__class__.__class__
Out[28]: type

In [29]: foo.__class__.__class__
Out[29]: type

所以说, 元类就是创建类的材料, 或者称为类工厂, typePython中内置的元类, 但是我们也可以创建我们自己的元类.

1.4 __metaclass__属性

我们可以在编写一个类的时候加入__metaclass__属性, 那么Python将会使用该属性来创建类.

1
2
3
class Foo:
__metaclass__ = something...
[...]

首先我们编写了class Foo:, 但是这个类对象Foo还没有被加载进内存.Python将会在类中查找__metaclass__属性, 如果找到了, 就用该属性值来创建类.如果没找到, 就用type来进行创建.
那么, 我们需要在__metaclass__属性中写什么呢?
某些能够创建类的东西.
什么能够创建类?
type

1.5 自定义元类

使用元类最主要的目的就是为了能够在创建一个类时, 自动的改变其行为.
想象一个很蠢的需求: 你需要将你模块儿中的所有类的属性都改成大写(内置属性除外). 有很多种方式来做这件事情, 在这里我们就可以使用__metaclass__属性.
首先, 我们使用一个函数:

1
2
3
4
5
6
7
8
9
10
def upper_attr(futrue_class_name, future_class_parrent, future_class_attr):
uppercase_attr = {}
for name, value in future_class_attr.items():
if not name.startswith("__"):
uppercase_attr[name.upper()] = value
else:
uppercase_attr[name] = value
return type(futrue_class_name, future_class_parrent, uppercase_attr)

__metaclass__ = upper_attr # 写在这里也就意味着整个模块儿的__metaclass__都是upper_attr

这样写感觉不是很面向对象的样子, 我们把这段代码丢到__new__方法中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class UpperAttrMetaclass(type):
"""
__new__方法在__init__方法之前调用, 是创建类对象的方法, 并且返回一个类对象
其第一个参数必须是cls
"""
def __new__(cls, future_class_name,
future_class_parents, future_class_attr):

uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return type(future_class_name, future_class_parents, uppercase_attr)

其实type也是有__new__方法的, 那么我们就可以这样写:

1
return type.__new__(cls, future_class_name, future_class_parents, uppercase_attr)

super也可以, 或者说更推荐使用super:

1
return super(UpperAttrMetaclass, cls).__new__(cls, future_class_name, future_class_parents, uppercase_attr)

最终我们将代码进行一下简化:

1
2
3
4
5
6
7
8
9
class UpperAttrMetaclass(type):
def __new__(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val
return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)

到这里就是元类的所有内容了.元类其实并不难, 只不过应用元类的场景下会有各种各样的内省, 继承以及乱七八糟的类属性.
元类其实就做了这些事情:

  • 拦截类的创建
  • 修改类
  • 返回修改的类

除了元类意外, 装饰器以及猴子补丁, 都可以改变一个类的行为.

1.6 代码中的__metaclass__

在我们编写自己的代码时, 肯定没有上面儿写的那么简单, 但是也不会更加的复杂.
在Java中, List没有append方法,但是有add方法, 我想要将add方法添加到我们自定义的List对象中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyAddListMetaclass(type):
def __new__(cls, class_name, bases, attributes):
# 在这里得调用list的append方法, 那么就需要使用多重继承
attributes["add"] = lambda self, values: self.append(values)
return super(MyAddListMetaclass, cls).__new__(cls, class_name, bases, attributes)


class MyAddList(list, metaclass=MyAddListMetaclass):
pass


if __name__ == "__main__":
my_list = MyAddList()
my_list.add("1")
print(my_list)

上面的代码虽然在正常情况下根本不会这样来写, 但是却是一个非常好的例子来说明我们怎么去使用__metaclass__.
此外, 在django中考虑到python2/3的兼容性, 一般会这样来写:

1
2
3
4
from django.utils import six

class MyAddList2(six.with_metaclass(MyAddListMetaclass, list)):
pass

既然元类能够改变一个类的产生, 那么必然能够在类被创建出来之前拿到该类的所有属性以及方法.
假如MyAddSubList继承自MyAddList, 而后我们在MyAddListMetaclassattributes进行输出:

1
2
3
4
5
6
7
8
class MyAddSubList(MyAddList):
foo = "Bar"

def hello(self):
return "hello~"
Output:
{'__module__': '__main__', '__qualname__': 'MyAddList'}
{'__module__': '__main__', '__qualname__': 'MyAddSubList', 'hello': <function MyAddSubList.hello at 0x7f3e5f12bd08>, 'foo': 'Bar'}

得到了非常完整的信息, 上面的这种继承体系, 在DjangoORM以及Form中, 应用相当广泛.
到这里元类这个东西也就到这里.下面开始正式的剖析Form的源码.


2. Form源码剖析

2.1 Form类

django.forms.forms.Form中的Form类, 其实就是一层外衣而已:

1
2
class Form(six.with_metaclass(DeclarativeFieldsMetaclass, BaseForm)):
""" """

这个类连pass都没有, 看起来也比较神奇.

2.2 DeclarativeFieldsMetaclass

Metaclass类才是重头戏.DeclarativeFieldsMetaclass继承自MediaDefiningClass, 该类继承于type. 在__new__方法中并没有做太多的事情:

1
2
3
4
5
6
class MediaDefiningClass(type):
def __new__(mcs, name, bases, attrs):
new_class = super(MediaDefiningClass, mcs).__new__(mcs, name, bases, attrs)
if 'media' not in attrs:
new_class.media = media_property(new_class)
return new_class

再来看DeclarativeFieldsMetaclass

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
50
51
52
class DeclarativeFieldsMetaclass(MediaDefiningClass):
"""
Metaclass that collects Fields declared on the base classes.
"""
def __new__(mcs, name, bases, attrs):
# Collect fields from current class.
current_fields = []
"""收集自定义Form类中的相关类Field属性"""
for key, value in list(attrs.items()):
if isinstance(value, Field):
current_fields.append((key, value))
attrs.pop(key)
"""
此时current_fields长这个样子
[('age', <django.forms.fields.CharField at 0x7fbec2aea080>),
('name', <django.forms.fields.CharField at 0x7fbec2957f60>)]
根据Field的creation_counter属性来进行排序, 该属性其实就是记录一个Field被实例化的次数.
class Field(object):
creation_counter = 0
self.creation_counter = Field.creation_counter
Field.creation_counter += 1
大致的代码就是这样, 可以看到每实例化一次, 相应的self.creation_counter就会+1
用来保证排序之后的顺序就是我们定义Field的顺序
"""
current_fields.sort(key=lambda x: x[1].creation_counter)
# tuple组成的list可以很方便的转换成OrderedDict
attrs['declared_fields'] = OrderedDict(current_fields)
# 调用父类__new__方法, 如果attrs中有media这个属性的话, 将会得到一个新的类对象
new_class = super(DeclarativeFieldsMetaclass, mcs).__new__(mcs, name, bases, attrs)

# Walk through the MRO.
declared_fields = OrderedDict()
for base in reversed(new_class.__mro__):
# Collect fields from base class.
if hasattr(base, 'declared_fields'):
declared_fields.update(base.declared_fields)

# Field shadowing.
for attr, value in base.__dict__.items():
if value is None and attr in declared_fields:
declared_fields.pop(attr)

new_class.base_fields = declared_fields
new_class.declared_fields = declared_fields
# 两个属性中保存的是同一个东西, 都是由我们写的Field所组成的OrderDict
"""
OrderedDict([('name', <django.forms.fields.CharField at 0x7fe96be7f160>),
('age', <django.forms.fields.CharField at 0x7fe96b396f28>)])
长这个样子
"""

return new_class

Metaclass的作用到这里也就结束了, 所做的事情无非就是将子类中的属性通过某些手段包装成一个OrderDict, 然后返回回去.使用的话是在多重继承中的BaseForm.

2.3 BaseForm

BaseForm中的代码比较长, 挑一些有代表意义的代码进行剖析.
首先是类属性, 不继续阅读的话凭字面意思还是无法理解的:

1
2
3
4
5
class BaseForm(object):
default_renderer = None
field_order = None
prefix = None
use_required_attribute = True

  • __init__方法:
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
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=None,
empty_permitted=False, field_order=None, use_required_attribute=None, renderer=None):
"""
通常我们会这样使用Form: form = TestForm(json.loads(request.body)), 当然在DRF下不用写这么多.表单数据最终会变成self.data

"""
self.is_bound = data is not None or files is not None # data不为None时为True
self.data = data or {}
self.files = files or {}
self.auto_id = auto_id
if prefix is not None:
self.prefix = prefix
self.initial = initial or {}
self.error_class = error_class # ErrorList这个类有点儿意思, 什么时候读一遍
self.label_suffix = label_suffix if label_suffix is not None else _(':')
self.empty_permitted = empty_permitted
self._errors = None # Stores the errors after clean() has been called.

# 就在这里, 将Metaclass产生的OrderDict进行了深度copy.
self.fields = copy.deepcopy(self.base_fields)
self._bound_fields_cache = {}
# 在初始化中的唯一方法调用(如果不算renderer的话)
self.order_fields(self.field_order if field_order is None else field_order)

if use_required_attribute is not None:
self.use_required_attribute = use_required_attribute

# 这里的代码与renderer页面渲染有关, 直接干掉不看

反正大部分的实例属性都是None, 有价值的信息包括is_bound, data, error_class, fields以及self.order_fields该方法的调用.

  • order_fields方法
    该方法在调用时参数, field_order为None, 所以该函数就直接返回了, 也没啥看的
1
2
3
4
def order_fields(self, field_order):
if field_order is None:
return
...

到这里初始化也就结束了, 然后我们调用form.is_valid来对数进行验证.

  • is_valid方法
1
2
3
def is_valid(self):
# 当我们的data为None时, self.is_bound为False, 将会直接返回False
return self.is_bound and not self.errors
  • errors属性, 代码不贴了, 反正是调用full_clean方法

  • full_clean方法, 这个还是要贴一下的

1
2
3
4
5
6
7
8
9
10
11
def full_clean(self):
self._errors = ErrorDict()
if not self.is_bound: # Stop further processing.
return
self.cleaned_data = {}
# self.empty_permitted在我们的使用场景下为False, 所以该流程直接pass
if self.empty_permitted and not self.has_changed():
return
self._clean_fields()
self._clean_form()
self._post_clean()

三个当个连续调用, 那就一个一个来呗.

  • _clean_fields方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def _clean_fields(self):
for name, field in self.fields.items():
if field.disabled:
value = self.get_initial_for_field(field, name)
else:
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
try:
if isinstance(field, FileField):
initial = self.get_initial_for_field(field, name)
value = field.clean(value, initial)
else:
value = field.clean(value)
self.cleaned_data[name] = value
"""
这个地方会对我们自定义的clean_ + 字段名的函数进行调用
比如 clean_name, clean_age, 在某些场景下我们需要对数据进行验证完成之后再进行一些处理, 就可以在我们自己的Form类中添加字段相对应的clean_方法.
"""
if hasattr(self, 'clean_%s' % name):
value = getattr(self, 'clean_%s' % name)()
self.cleaned_data[name] = value
except ValidationError as e:
self.add_error(name, e)

其实比较重要的就是clean方法的调用, 在clean方法中, 即进行了数据的验证, 其实调用的是Field的数据验证方法. self._clean_form()实际调用了self.clean方法, 为Formclean方法.self._post_clean()用于models中, Form类本身没有做一些事情, 应该是ModelForm来继承并重写该方法.
那么到这里其实主要的代码也就结束了, 剩余的就是如何去使用这些函数来写出优雅的代码.

3. 总结

回顾整个Form的源码, 其关键点仍然在于metaclass, 使得父类能够获取子类的类属性(这是一个相当重要的特性, 我认为), 并对其进行某些相关操作, 最终来达到我们的目的.metaclassDjango框架中使用的非常之多, 不仅仅是ORM(当然ORM的源码分析起来更为扎实, 其代码量粗略来看也有接近万行, 几乎就是一个中型业务的代码量了).