(三十)Flask之wtforms库【剖析源码上篇】

每篇前言:

|-----------------------------------------------------------------------|
| 🏆🏆作者介绍:【孤寒者】---CSDN全栈领域优质创作者、HDZ核心组成员、华为云享专家Python全栈领域博主、CSDN原力计划作者 |


如果在项目中需要使用wtforms,那么脑海中应该有个清晰的用wtforms库写类的架子:

python 复制代码
# 半伪代码:
class LoginForm(Form):
	name = StringField(正则=[验证规则1, 验证规则2, ], 插件=Input框)
	password = StringField(正则=[验证规则1, 验证规则2, ], 插件=Password框)

wtforms实现原理主要从三个方面进行剖析:form类创建过程、实例化过程、验证过程。从整体看其实现原理就是将每个类别的功能(如Filed、validate、meta等)通过form进行组织、封装,在form类中调用每个类别对象的方法实现数据的验证和html的渲染。这里先总结下验证流程:

  1. for循环每个字段;
  2. 执行该字段的pre_validate钩子函数;
  3. 执行该字段参数的validators中的验证方法和validate_字段名钩子函数(如果有);
  4. 执行该字段的post_validate钩子函数;
  5. 完成当前字段的验证,循环下一个字段,接着走该字段的2、3、4流程,直到所有字段验证完成;

以(二十八)篇文章中的【用户登录验证】这部分的代码为例一点点进行剖析~

作者写上一篇文章很大一个目的是,让大家在分析代码时,一看定义类的部分,就要条件反射般的想到元类这一玩意!

当前类有没有指定自定义元类,如果没有指定自定义元类,那么当前类继承的父类呢?继承的父类的父类呢???

【如果有的话,在代码执行到下图箭头所指时,就会触发对应自定义元类的init魔法方法!】

所以当代码执行到上图箭头所指位置时,究竟执行了什么逻辑,就要往这个类的父类追踪看一看:

python 复制代码
"""
上图箭头所指那句等价于:
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就是前面说的:

python 复制代码
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魔法方法~】

    python 复制代码
    for item in form:
    	print(item) 
python 复制代码
        self._fields = {
            'name': simple.StringField(),
            'pwd': simple.PasswordField()
        }

然后print就会执行每个字段的str魔法方法,所以就会打印user和pwd的input框前端代码。

相关推荐
木觞清1 小时前
Django学习第三天
python·学习·django
电饭叔1 小时前
《python程序语言设计》2018版第5章第52题利用turtle绘制sin函数
开发语言·python
YCCX_XFF212 小时前
ImportError: DLL load failed while importing _imaging: 操作系统无法运行 %1
开发语言·python
FutureUniant4 小时前
GitHub每日最火火火项目(7.7)
python·计算机视觉·ai·github·视频
杰哥在此4 小时前
Java面试题:讨论持续集成/持续部署的重要性,并描述如何在项目中实施CI/CD流程
java·开发语言·python·面试·编程
PY1785 小时前
Python的上下文管理器
数据库·python·oracle
Struggle to dream6 小时前
Python编译器的选择
开发语言·python
刘铸纬6 小时前
Golang中defer和return顺序
开发语言·后端·golang
爱看书的小沐6 小时前
ASCII码对照表(Matplotlib颜色对照表)
python·matplotlib·rgb·ascii·colormap·颜色对照表·颜色映射
算法金「全网同名」7 小时前
算法金 | 推导式、生成器、向量化、map、filter、reduce、itertools,再见 for 循环
python·机器学习·数据分析