当前位置: 首页 > news >正文

(三十)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的渲染。这里先总结下验证流程:

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

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

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

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

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

在这里插入图片描述

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

"""
上图箭头所指那句等价于:
class NewBase(BaseForm, metaclass=FormMeta):passclass 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 thevalue.:param meta:A meta instance which is used for configuration and customizationof WTForms behaviors."""if prefix and prefix[-1] not in '-_;:/.':prefix += '-'self.meta = metaself._prefix = prefixself._errors = Noneself._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框前端代码。

相关文章:

  • 动态规划02(Leetcode62、63、343、96)
  • C语言——文件
  • Elastic字段映射(_source,doc_value,fileddata,index,store)
  • 【C语言】解决C语言报错:Array Index Out of Bounds
  • EasyExcel自定义处理器扩展指定行修改样式包括字体颜色
  • 没等来百度惊艳的All in AI,却等来了国产之光的盘古大模型 5.0
  • java-正则表达式 1
  • Android使用MPAndroidChart 绘制折线图
  • Java学习 - MySQL存储过程、函数和触发器练习实例
  • Java中的设计模式:实战案例分享
  • labelme使用笔记:目标检测数据集标注和语义分割数据集批量生成
  • kafka第二篇——主题创建,主题分区副本分配策略
  • PWR电源控制
  • IDEA services模块无法启动springboot服务(添加了springboot但是为空白)
  • HTTP/2 的 ALPN(应用层协议协商)
  • [NodeJS] 关于Buffer
  • bearychat的java client
  • canvas 五子棋游戏
  • DataBase in Android
  • E-HPC支持多队列管理和自动伸缩
  • express如何解决request entity too large问题
  • HTTP请求重发
  • Js基础——数据类型之Null和Undefined
  • leetcode46 Permutation 排列组合
  • PHP的类修饰符与访问修饰符
  • Python_网络编程
  • React组件设计模式(一)
  • ⭐ Unity + OpenCV 实现实时图像识别与叠加效果
  • V4L2视频输入框架概述
  • 成为一名优秀的Developer的书单
  • 当SetTimeout遇到了字符串
  • 规范化安全开发 KOA 手脚架
  • 互联网大裁员:Java程序员失工作,焉知不能进ali?
  • 基于阿里云移动推送的移动应用推送模式最佳实践
  • 计算机常识 - 收藏集 - 掘金
  • 计算机在识别图像时“看到”了什么?
  • 前端每日实战 2018 年 7 月份项目汇总(共 29 个项目)
  • 软件开发学习的5大技巧,你知道吗?
  • 十年未变!安全,谁之责?(下)
  • 使用 5W1H 写出高可读的 Git Commit Message
  • 系统认识JavaScript正则表达式
  • Python 之网络式编程
  • 回归生活:清理微信公众号
  • 直播平台建设千万不要忘记流媒体服务器的存在 ...
  • 组复制官方翻译九、Group Replication Technical Details
  • ​​​​​​​ubuntu16.04 fastreid训练过程
  • ​3ds Max插件CG MAGIC图形板块为您提升线条效率!
  • # 安徽锐锋科技IDMS系统简介
  • # 消息中间件 RocketMQ 高级功能和源码分析(七)
  • (BAT向)Java岗常问高频面试汇总:MyBatis 微服务 Spring 分布式 MySQL等(1)
  • (博弈 sg入门)kiki's game -- hdu -- 2147
  • (二)linux使用docker容器运行mysql
  • (翻译)Entity Framework技巧系列之七 - Tip 26 – 28
  • (附源码)springboot家庭财务分析系统 毕业设计641323
  • (三)Honghu Cloud云架构一定时调度平台