(三十)Flask之wtforms库【剖析源码上篇】
每篇前言:
🏆🏆作者介绍:【孤寒者】—CSDN全栈领域优质创作者、HDZ核心组成员、华为云享专家Python全栈领域博主、CSDN原力计划作者
- 🔥🔥本文已收录于Flask框架从入门到实战专栏:《Flask框架从入门到实战》
- 🔥🔥热门专栏推荐:《Python全栈系列教程》、《爬虫从入门到精通系列教程》、《爬虫进阶+实战系列教程》、《Scrapy框架从入门到实战》、《Flask框架从入门到实战》、《Django框架从入门到实战》、《Tornado框架从入门到实战》、《前端系列教程》。
- 📝📝本专栏面向广大程序猿,为的是大家都做到Python全栈技术从入门到精通,穿插有很多实战优化点。
- 🎉🎉订阅专栏后可私聊进一千多人Python全栈交流群(手把手教学,问题解答); 进群可领取Python全栈教程视频 + 多得数不过来的计算机书籍:基础、Web、爬虫、数据分析、可视化、机器学习、深度学习、人工智能、算法、面试题等。
- 🚀🚀加入我一起学习进步,一个人可以走的很快,一群人才能走的更远!
如果在项目中需要使用wtforms,那么脑海中应该有个清晰的用wtforms库写类的架子:
# 半伪代码:
class LoginForm(Form):
name = StringField(正则=[验证规则1, 验证规则2, ], 插件=Input框)
password = StringField(正则=[验证规则1, 验证规则2, ], 插件=Password框)
wtforms实现原理主要从三个方面进行剖析:form类创建过程、实例化过程、验证过程。从整体看其实现原理就是将每个类别的功能(如Filed、validate、meta等)通过form进行组织、封装,在form类中调用每个类别对象的方法实现数据的验证和html的渲染。这里先总结下验证流程:
- for循环每个字段;
- 执行该字段的pre_validate钩子函数;
- 执行该字段参数的validators中的验证方法和validate_字段名钩子函数(如果有);
- 执行该字段的post_validate钩子函数;
- 完成当前字段的验证,循环下一个字段,接着走该字段的2、3、4流程,直到所有字段验证完成;
以(二十八)篇文章中的【用户登录验证】这部分的代码为例一点点进行剖析~
作者写上一篇文章很大一个目的是,让大家在分析代码时,一看定义类的部分,就要条件反射般的想到元类这一玩意!
当前类有没有指定自定义元类,如果没有指定自定义元类,那么当前类继承的父类呢?继承的父类的父类呢???
【如果有的话,在代码执行到下图箭头所指时,就会触发对应自定义元类的init魔法方法!】
所以当代码执行到上图箭头所指位置时,究竟执行了什么逻辑,就要往这个类的父类追踪看一看:
"""
上图箭头所指那句等价于:
class NewBase(BaseForm, metaclass=FormMeta):
pass
class Form(NewBase):
pass
"""
这就找到了!父类Form的父类NewBase自定义了元类FormMeta,所以就会执行这个元类的init魔法方法:
上图所示代码中,
type.__init__(cls, name, bases, attrs)
是在调用基类type
的__init__
方法。这样的调用是为了确保基类的
__init__
方法被适当地调用,以便正确地初始化类对象。
上图cls就是当前类—LoginForm类~
所以代码执行到最开始那句时:
【类里已经有了两个字段,而且值为None】
代码继续往下执行:
当上图部分代码都执行完后,LoginForm类的user和pwd的值会是下图这俩吗?
请注意这俩都对应实例化了一个类,所以要具体分析一下:
先来看user,它继承的类有没有指定自定义元类:
在这里插入图片描述
会发现到顶了也没有指定的,所以继续会执行类的new魔法方法,一个个类往上找:
这个if不成立,所以走else。返回了一个使用 UnboundField
类创建的对象实例,参数有cls,就是当前类StringField,并携带着StringField的所有参数。
怎么理解这个操作呢?
因为StringField功能有限,这里想要多加一个功能,就通过给StringField外面再套一层,这就是UnboundField,它实现了计数器的功能(下面会讲这是个啥)!
所以LoginForm类里的name和pwd的值应该是:
继续来看看UnboundField类:
所以当LoginForm类的代码执行完,LoginForm类就已经有了注释部分那些值:
代码继续执行:
当代码执行到下图红色箭头所示时,这是LoginForm类的实例化,代码内部执行流程就如下图红框所示:
一步步来,先来看FormMeta的call魔法方法:
当代码执行到上图红色箭头所示位置时,LoginForm类的_unbound_fields
属性就有值了:
继续看call方法:
LoginForm类里面是没有Meta的,继续看Form类,发现有:
当代码执行到上图红色箭头所示位置时,LoginForm类的_wtforms_meta
属性也有值了:
call方法结束,接下来来看LoginForm的new魔法方法,会发现没有;
所以继续来看LoginForm的init魔法方法:
先看上图红框所示部分代码,重点是 —> 最后一句调用父类的初始化方法,所以看父类BaseForm的init魔法方法中对传入的参数做了什么操作:
注意参数
self._unbound_fields
就是前面说的:
class BaseForm(object):
"""
Base Form Class. Provides core behaviour like field construction,
validation, and data and error proxying.
"""
def __init__(self, fields, prefix='', meta=DefaultMeta()):
"""
:param fields:
A dict or sequence of 2-tuples of partially-constructed fields.
:param prefix:
If provided, all fields will have their name prefixed with the
value.
:param meta:
A meta instance which is used for configuration and customization
of WTForms behaviors.
"""
if prefix and prefix[-1] not in '-_;:/.':
prefix += '-'
self.meta = meta
self._prefix = prefix
self._errors = None
self._fields = OrderedDict()
if hasattr(fields, 'items'):
fields = fields.items()
translations = self._get_translations()
extra_fields = []
if meta.csrf:
self._csrf = meta.build_csrf(self)
extra_fields.extend(self._csrf.setup_form(self))
"""
fields:
[
('name', UnboundField(simple.StringField, *args, **kwargs, creation_counter=1)),
('pwd', UnboundField(simple.PasswordField, *args, **kwargs, creation_counter=2)),
]
extra_fields(下面我会讲一下这个是干啥的)我是没有传的,所以下面循环就是循环fields这个列表~
"""
for name, unbound_field in itertools.chain(fields, extra_fields):
options = dict(name=name, prefix=prefix, translations=translations)
"""
name: 'name'; unbound_field = UnboundField(simple.StringField, *args, **kwargs, creation_counter=1)
下面这句就是把simple.StringField拿出来实例化:
field = simple.StringField()
"""
field = meta.bind_field(self, unbound_field, options)
self._fields[name] = field
"""
上述for循环执行完后:
self._fields = {
'name': simple.StringField(),
'pwd': simple.PasswordField()
}
"""
所以,当代码执行到此,LoginForm对象(因为执行init就完成了类的实例化操作)里就有了值(注意:上面都是往LoginForm类里加的数据):
所以,回到Form类的init魔法方法,继续往下看:
当上图红框部分代码执行完毕后,LoginForm对象里就又通过setattr设置了两个实例属性(将所有的field拆出来给到了LoginForm对象):
这样我们才能执行的form.name或者form.pwd这些。
还差最后一句,init就也执行完了,那么最后一局self.process()是干了啥呢?
它就是给每个input加默认值以及做验证操作的。
如下就是讲了下如何给input框加默认值?以及如何生成的input框?
页面上也是form.user这样就生成了对应的input框:
如果想给这个input一个默认值呢?
接下来就看看为何执行simple.StringField().__str__
就生成了input框:
没有str魔法方法,继续看父类Field:
这样就会触发当前类的call魔法方法:
进去:
field.widget()就是StringField对象里的widget(插件)加括号,就会触发插件对象的call魔法方法,所以继续进:
所以总结这个流程,生成input框的任务是交给了TextInput插件去做的:
-
小问题:form是否支持for循环?
【Tips:一个对象支持for循环,那么这个类内部肯定实现了iter魔法方法~】
for item in form: print(item)
self._fields = {
'name': simple.StringField(),
'pwd': simple.PasswordField()
}
然后print就会执行每个字段的str魔法方法,所以就会打印user和pwd的input框前端代码。