日度归档:2018年7月12日

WTForms自定义验证方法(行内验证器)是如何被调用的?

这篇文章基于我在知乎上的这个回答,进行了相应的简化处理,放到这里做个备份。

万能的回答

答案在源码里。

简单的回答

WTForms会在你对表单实例调用Form.validate()方法时收集所有的行内验证方法,然后在对每个字段调用Field.validate()方法验证时将这些自定义行内验证方法一并和通过validators参数传入的验证器列表一起调用,进行验证。因为WTForms在调用时把对应的field作为参数传入了行内验证方法,所以你可以在自定义验证方法中通过field.data获取对应字段的数据。

深入的回答

WTForms会在你对表单实例调用Form.validate()方法时收集所有的行内验证方法。在Form类中的validate()方法定义中,你可以看到WTForms是如何收集这些行内验证方法的:

class Form(with_metaclass(FormMeta, BaseForm)): 
   ....
   def validate(self):
        extra = {}
        for name in self._fields:
            inline = getattr(self.__class__, 'validate_%s' % name, None)
            if inline is not None:
                extra[name] = [inline]

        return super(Form, self).validate(extra)

源码位置:github.com/wtforms/wtfo

这里迭代所有的字段属性,然后表单类中是否包含validate_字段名形式的方法。如果有,那么就添加到extra字段里,这个字段被传递到BaseForm类的validate()方法中。在BaseForm类的validate()方法中,WTForms迭代所有字段,并对每个字段调用Field.validate()方法验证字段,继续传入自定义的行内验证方法:

class BaseForm(object):
    ...
    def validate(self, extra_validators=None):
        self._errors = None
        success = True
        for name, field in iteritems(self._fields):  # 迭代字段名和字段对象
            if extra_validators is not None and name in extra_validators:  # 判断当前迭代字段是否存在自定义验证器
                extra = extra_validators[name]
            else:
                extra = tuple()
            if not field.validate(self, extra):  # 调用字段类的验证方法进行验证,传入自定义验证器
                success = False
        return success

源码位置:github.com/wtforms/wtfo

而在字段基类Field的validate()方法中,WTForms使用itertool模块提供的chain()函数把你实例化字段类时传入的验证器(self.validators)和自定义行内验证器(extra_validators)连接到一起:

class Field(object):    
    ...
    def validate(self, form, extra_validators=tuple()):
        ...
        # Run validators
        if not stop_validation:
            chain = itertools.chain(self.validators, extra_validators)  # 合并两类验证器
            stop_validation = self._run_validation_chain(form, chain)  # 运行验证器
        ...

源码位置:github.com/wtforms/wtfo

连接起来的所有验证器赋值为chain变量,并传入到self._run_validation_chain(form, chain)进行进一步调用:

class Field(object):    
    ...
    def _run_validation_chain(self, form, validators):
        for validator in validators:
            try:
                validator(form, self)  # 传入字段类本身作为第二个参数

源码位置:github.com/wtforms/wtfo

这个方法迭代所有的验证器对字段数据进行验证。关键在于validator(form, self),可以看到这里传入了第二个参数self,即Field类本身,这也是为什么你可以在自定义验证方法中通过field.data获取当前字段数据。

Python中的下划线有多少种用法和含义?

这篇文章来自我在知乎上的这个回答,做个备份。

大概有10种。

如下:

# No.1
# 在交互式解释器中获取上一个语句执行的结果
# 比如:
# >>> 1+1
# 2
# >>> _
# 2
# >>> _ * 5
# 10
_

# No.2
# 用来在函数、模块、包、变量名中分隔单词,增加可读性
var_foo_bar

# No.3
# 内部使用的变量、属性、方法、函数、类或模块(约定)
# from foo import * 不会导入以下划线开头的对象
_var

# No.4
# 避免和保留的关键字冲突(约定)
# 比如:class_、type_
var_

# No.5
# 在类内的私有变量(private)
# 类外部无法直接使用原名称访问
# 需要通过instance._ClassName__var的形式访问(name mangling)
__var

# No.6(这一条存疑)
# 在类内的保护变量
_var_

# No.7
# Python内置的“魔法”方法或属性
# 你也可以自己定义,但一般不推荐
# 比如:__init__, __file__, __main__
__var__

# No.8
# 作为内部使用的一次性变量
# 通常在循环里使用
# 比如:[_ for _ in range(10)]
# 或是用作占位,不实际使用的变量
# 比如:for _, a in [(1,2),(3,4)]: print a
_

# No.9
# i18n里作为gettext()的缩写
_()

# No.10
# 用来分隔数值以增加可读性(Python 3.6新增)
# 比如
# >>> num = 1_000_000 
# >>> num
# 1000000
1_000_000

参考链接: