背景
最近在使用wtform做后台的表单验证时候,跟很多框架使用的方法一样,它是结合ORM的对象提供校验, wtform 默认提供了不少的校验器,但是有的字段需要自己编写一些业务相关的校验器。
自定义的每个校验器的特点都是接受两个参数,form, field。
所以我们自己自定义校验器,校验器都必须是可调用对象即可,即函数,对象方法,都可以的。
比如function url_validate(form, field)
我们可以使用下面几种方式来实现
使用函数定制
def my_length_check(form, field):
if len(field.data) > 50:
raise ValidationError('Field must be less than 50 characters')
class MyForm(Form):
name = StringField('Name', [InputRequired(), my_length_check])
上面提供一个my_length_check()函数,用于验证name长达是否长于50个字符。
这个函数按照规定接受两个参数,form, field,然后就可以根据两个参数进行判断。
这样做是可以的,但是问题是:
- 如果我想自定义错误信息怎么办?而且里面的限制是50, 已经被写死了,我不能修改,如果我想改最小值,和最大值呢?
- 错误信息我也不能自定义?
所以, 这里只能固定接受两个参数,不能再传入自定参数,没得更多的自定义了,显然不行。
使用闭包
我们先简单了解一下闭包的概念:闭包能使局部变量在函数外被访问
先来看个例子:
def A():
num = 10 # 局部变量
print(num) # NameError: name 'num' is not defined
定义在函数内的变量是局部变量,局部变量的作用范围只能是函数内部范围内,它不能在函数外引用。
因为 num 是定义在函数内的局部变量,局部变量在函数外引用是失败的。
我们看看闭包的具体实现:
def A():
# A 是外围函数
msg = "I like python in A"
def B():
# B 是嵌套函数
print(msg)
return B
b = A()
# 输出 I like python in A
b()
正常理解下,函数中的局部变量仅在函数的执行期间可用,所以 A() 执行过后,我们会认为 A函数里面的 msg 变量将不再可被外界读取。然而,在这里我们发现 A() 执行完之后,得到的对象 b,在b()调用的时候 msg 变量的值正常输出了。
这就是闭包的作用,闭包使得局部变量在函数外能被访问。
回到我们刚才的例子,使用闭包给wtform增加校验器:
def length(min=-1, max=-1, message=None):
if not message:
message = 'Must be between %d and %d characters long.' % (min, max)
def _length(form, field):
l = field.data and len(field.data) or 0
if l < min or max != -1 and l > max:
raise ValidationError(message)
return _length
class MyForm(Form):
name = StringField('Name', [InputRequired(), length(max=50, message="长度不符合!")])
length函数是个闭包, 为什么是个闭包?正常情况下执行完length() min, max, message参数就会被回收,不再存在,但是因为闭包的特性,_length 能读取到里面的min, max, message。
这个length(min, max, message) 函数,调用的时候,传入了更多的参数来灵活决定校验器,因为他里面就是返回一个_length的校验器,这个校验器还是遵循规则,只接受form, field 参数, 但是在length() 这个外层却能接受更多参数,而这些参数也能被内层的_length()所使用。
所以最后实际上校验器的调用是这样的:
length(max=50, message="长度不符合!")(form, field)
length() 就是一个闭包,闭包本质上是一个函数,它有两部分组成,_length 函数和变量 min, max, message。闭包使得这些变量的值始终保存在内存中。
使用 __call__() 实现
__call__() 是一个特殊的魔术方法, 它可以让类的实例的行为表现的像函数一样。所以如果在类中实现了 __call__() 方法,那么实例对象也将成为一个可调用对象。
class Length(object):
def __init__(self, min=-1, max=-1, message=None):
self.min = min
self.max = max
if not message:
message = u'Field must be between %i and %i characters long.' % (min, max)
self.message = message
def __call__(self, form, field):
l = field.data and len(field.data) or 0
if l < self.min or self.max != -1 and l > self.max:
raise ValidationError(self.message)
class MyForm(Form):
name = StringField('Name', [InputRequired(), Length(max=50)])
这次我们使用类来实现,首先实例化这个校验器
len_validator = Length(max=50, message="长度不合符哟!")
len_validator(form, field)
实例化对象后返回的len_validator对象,由于我们实现了__call__()方法,说明len_validator这个对象能像函数一样被调用,而__call__()函数内部能读取对象内部的变量,因此对象的min, max, message属性它都能读取到。而且它接受的参数是form, field,符合扩展校验器规范。
闭包和__call__()的其他应用:单例模式
使用闭包
def singleton(cls):
_instance = {}
def inner():
if cls not in _instance:
_instance[cls] = cls()
return _instance[cls]
return inner
@singleton
class Cls(object):
def __init__(self):
pass
cls1 = Cls()
cls2 = Cls()
print(id(cls1) == id(cls2))
Cls前面增加了singleton装饰器,所以定义Cls的时候相当于 Cls = singleton(Cls), 而singleton(Cls) 返回的是inner函数对象,这个inner()就是一个闭包,能读取到 _instance 属性。 所以每次执行Cls()相当于执行inner()函数,这个inner() 每次都能读取singleton() 初始化过的闭包变量_instance, 判断_instance是否被填充,从而作出单例返回。
使用__call__()
class Singleton(object):
def __init__(self, cls):
self._cls = cls
self._instance = {}
def __call__(self):
if self._cls not in self._instance:
self._instance[self._cls] = self._cls()
return self._instance[self._cls]
@Singleton
class Cls2(object):
def __init__(self):
pass
cls1 = Cls2()
cls2 = Cls2()
print(id(cls1) == id(cls2))
Cls类定义前面增加了装饰器,所以定义Cls的时候相当于 Cls = singleton(Cls), 而singleton(Cls) 返回的是singleton的实例对象(假设是singleton_obj),因此Cls = singleton_obj, 当执行Cls()相当于执行singleton_obj(), 这时就会触发到__call__()方法,到里面判断_instance是否被填充,从而作出单例返回
总结
对于使用闭包和__call__特性,总结一下:
- 闭包避免了使用全局变量,此外,闭包允许将函数与其所操作的某些数据(环境)关连起来, 这一点跟面向对象编程是很像的。
- __call__() 是能让对象实例变成可执行的魔术方法,能读取实例绑定的其他变量
关于我
如果文章对你有收获,可以收藏转发,这会给我一个大大鼓励哟!
另外可以关注我公众号【码农富哥】 (coder2025),我会持续输出原创的算法,计算机基础文章!
来源:CSDN
作者:a519640026
链接:https://blog.csdn.net/a519640026/article/details/104215333