Python __call__详解

可以调用的对象

关于 __call__ 方法,不得不先提到一个概念,就是可调用对象(callable),我们平时自定义的函数、内置函数和类都属于可调用对象,但凡是可以把一对括号()应用到某个对象身上都可称之为可调用对象,判断对象是否为可调用对象可以用函数 callable

如果在类中实现了 __call__ 方法,那么实例对象也将成为一个可调用对象,

你也许已经知道,在Python中,方法也是一种高等的对象。这意味着他们也可以被传递到方法中就像其他对象一样。这是一个非常惊人的特性。 在Python中,一个特殊的魔术方法可以让类的实例的行为表现的像函数一样,你可以调用他们,将一个函数当做一个参数传到另外一个函数中等等。这是一个非常强大的特性让Python编程更加舒适甜美。 __call__(self, [args...])

允许一个类的实例像函数一样被调用。实质上说,这意味着 x() 与 x.__call__() 是相同的。注意 __call__ 参数可变。这意味着你可以定义 __call__ 为其他你想要的函数,无论有多少个参数。

__call__ 在那些类的实例经常改变状态的时候会非常有效。调用这个实例是一种改变这个对象状态的直接和优雅的做法。用一个实例来表达最好不过了:

class Entity:
‘‘‘调用实体来改变实体的位置。‘‘‘

def __init__(self, size, x, y):
    self.x, self.y = x, y
    self.size = size

def __call__(self, x, y):
    ‘‘‘改变实体的位置‘‘‘
    self.x, self.y = x, y

e = Entity(1, 2, 3) // 创建实例
e(4, 5) //实例可以象函数那样执行,并传入x y值,修改对象的x y

实例 --- Flask Wtform

在wtform的validators就使用了这个特性
wtform 定义字段的时候可以为每个字段添加校验器, 而每个校验器的特点都是接受两个参数,form, field。
所以我们可以自己自定义校验器,校验器是可调用对象即可,即函数,对象方法,都可以的。
比如function url_validate(form, field)

使用函数定制

下面提供一个my_length_check()函数,用于验证name长达是否长于50个字符。
这个函数按照规定接受两个参数,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])
使用闭包和工厂模式

为了解决上面能传入更多自定义参数, 更灵活定制校验,我们进行改造。
使用工厂模式,一个能生产校验器的工厂,而这个工厂能接受其他更灵活的参数,看例子:

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)])

这里涉及两个概念:

  • 工厂模式
    这个例子的length()是一个工厂,他的工作就是执行的时候,生产一个validator校验器,每次调用,传入不同参数,就会生产不同的校验器,所以叫做工厂模式。
  • 闭包
    同时它也是个闭包, 为什么是个闭包?正常情况下执行完length() min, max, message参数就会被回收,不在存在,但是为什么_length()能继续读取min, max, message变量,就是闭包的威力。闭包使得局部变量在函数外被访问成为可能。
    这里的 length() 就是一个闭包,闭包本质上是一个函数,它有两部分组成,_length 函数和变量 min, max, message。闭包使得这些变量的值始终保存在内存中。
    参考:一步一步教你认识Python闭包

这个length(min, max, message) 函数,调用的时候,传入了更多的参数来灵活决定校验器,因为他里面就是返回一个_length的校验器,这个校验器还是遵循规则,只接受form, field 参数, 但是在length() 这个外层却能接受更多参数,而这些参数也能被内层的_length()所使用。

class MyForm(Form):
    name = StringField(‘Name‘, [InputRequired(), length(max=50)])

这里的length(max=5)就是返回了个_length函数,wtform调用校验器的时候实际上就是这样调用的:

length(max=50)(form, field)
使用类方法__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)])

这次我们使用类来实现,首先实例化这个校验器Length(max=50), 这时返回的是对象实例,然后我们定义了方法__call__方法,说明实例对象是可以调用的, 最后的结果就是Length(max=50)(form, field)。
这样实现方法是也是很妙的。
说到这里,类实现方法跟闭包很像,都是把变量封装起来,让真正的校验器能读取到参数。

闭包避免了使用全局变量,此外,闭包允许将函数与其所操作的某些数据(环境)关连起来。这一点与面向对象编程是非常类似的,在面对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。

__call__其他作用

实例对象也可以像函数一样作为可调用对象来用,那么,这个特点在什么场景用得上呢?这个要结合类的特性来说,类可以记录数据(属性),而函数不行(闭包某种意义上也可行),利用这种特性可以实现基于类的装饰器,在类里面记录状态,比如,下面这个例子用于记录函数被调用的次数:

class Counter:
    def __init__(self, func):
        self.func = func
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        return self.func(*args, **kwargs)

@Counter
def foo():
    pass

for i in range(10):
    foo()

print(foo.count)  # 10

首先这里的@Counter是装饰器,执行起来顺序是 foo = Counter(foo), 实例化,把foo函数传到类Counter里面,并存到对象属性里面,然后返回foo = Counter实例。 这时foo已经是Counter实例,而不是本身foo函数。
当执行foo()的时候,其实已经变成了,执行__call__函数,而这个函数里面是执行了本身的self.func 即foo的实际逻辑, 而且加上了计算调用次数。这样就记录状态了。
太厉害了,这样的实现方式。

https://wtforms.readthedocs.io/en/stable/validators.html#custom-validators
https://stackoverflow.com/questions/9663562/what-is-the-difference-between-init-and-call-in-python
一步一步教你认识Python闭包
简述 initnewcall 方法

作者:大富帅
链接:https://www.jianshu.com/p/e1d95c4e1697
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

原文地址:https://www.cnblogs.com/jfdwd/p/11198767.html

时间: 2024-08-29 05:29:28

Python __call__详解的相关文章

python正则表达式详解

python正则表达式详解 正则表达式是一个很强大的字符串处理工具,几乎任何关于字符串的操作都可以使用正则表达式来完成,作为一个爬虫工作者,每天和字符串打交道,正则表达式更是不可或缺的技能,正则表达式的在不同的语言中使用方式可能不一样,不过只要学会了任意一门语言的正则表达式用法,其他语言中大部分也只是换了个函数的名称而已,本质都是一样的.下面,我来介绍一下python中的正则表达式是怎么使用的. 首先,python中的正则表达式大致分为以下几部分: 元字符 模式 函数 re 内置对象用法 分组用

python线程详解

#线程状态 #线程同步(锁)#多线程的优势在于可以同时运行多个任务,至少感觉起来是这样,但是当线程需要共享数据时,可能存在数据不同步的问题. #threading模块#常用方法:'''threading.currentThread():返回当前的线程变量threading.enumerate():返回一个包含正在运行的线程的list,正在运行指:线程启动后,结束前,不包含启动前和终止后的线程threading.activeCount():返回正在运行的线程数量,与len(threading.en

python difflib详解

difflib -帮助进行差异化比较 这个模块提供的类和方法用来进行差异化比较,它能够生成文本或者html格式的差异化比较结果,如果需要比较目录的不同,可以使用filecmp模块. class difflib.SequenceMatcher 这是可以用来比较任何类型片段的类,只要比较的片段是可hash的,都可以用来比较,使用非常灵活.他源于1980,s的“完形匹配算法”,并且进行了一系列的优化和改进. 通过对算法的复杂度比较,它由于原始的完形匹配算法,在最坏情况下有n的平方次运算,在最好情况下,

转 python数据类型详解

python数据类型详解 目录 1.字符串 2.布尔类型 3.整数 4.浮点数 5.数字 6.列表 7.元组 8.字典 9.日期 1.字符串 1.1.如何在Python中使用字符串 a.使用单引号(') 用单引号括起来表示字符串,例如: str='this is string'; print str; b.使用双引号(") 双引号中的字符串与单引号中的字符串用法完全相同,例如: str="this is string"; print str; c.使用三引号(''') 利用三

Python列表详解

Python列表详解: 创建一个列表,只要把逗号分隔的不同数据项使用方括号括起来即可. 比如:    list = [1, 2, 3, 4, 5 ]; 与字符串的索引一样,列表索引从0开始. Python列表函数即方法: Python所包含的函数: 1.cmp() 描述: cmp()用于比较两个列表的元素. 语法: cmp (list1,list2) 返回值: 如果比较的元素是同类型的,则比较其值,返回结果. 如果两个元素不是同一种类型,则检查它们是否是数字. 如果是数字,执行必要的数字强制类型

Python 递归函数 详解

Python 递归函数 详解   在函数内调用当前函数本身的函数就是递归函数   下面是一个递归函数的实例: 第一次接触递归函数的人,都会被它调用本身而搞得晕头转向,而且看上面的函数调用,得到的结果会是: 为什么会得出上面的结果呢?因为都把调用函数本身之后的代码给忘记了,就是else之后的python 代码. 实际此递归函数输出的是以下结果: 相信大家看到这里都有点蒙,小编也一样,我第一次看到这个递归函数时,只能理解到第一个结果.那是因为,大部分人在做事情的时候,中断第一件事,被安排去做第二件事

Python数据类型详解——列表

Python数据类型详解--列表 在"Python之基本数据类型概览"一节中,大概介绍了列表的基本用法,本节我们详细学一下列表. 如何定义列表:在[]内以英文里输入法的逗号,,按照索引,存放各种数据类型,每个位置代表一个元素. 回顾一下列表的特点: 1.可存放多个值. 2.按照从左到右的顺序定义列表元素,下标从0开始顺序访问,是有序的. 3.可修改指定索引位置对应的值,可变. 一.列表元素的增加操作 1.追加 用append方法将数据追加到列表的尾部 names = ['Kwan',

python面向对象详解(上)

创建类 Python 类使用 class 关键字来创建.简单的类的声明可以是关键字后紧跟类名: class ClassName(bases): 'class documentation string' #'类文档字符串' class_suite #类体 实例化 通过类名后跟一对圆括号实例化一个类 mc = MyClass() # instantiate class 初始化类 'int()'构造器 def __int__(self): pass 注意:self类似Java的this关键字作用,它代

python字典详解

字典是Python中唯一的內建的映射类型,可以存储任意对象的容器,比如:字符串,列表,元组,自定义对象等:字典由键(key)与值(value)组成,基本语法如下: {key:value, ... ...}字典中每个Key是唯一的,key必须是可哈希(后面我们介绍): 这节我们来看下字典基本知识点: 1>字典定义.遍历.修改:2>理解字典key:4>字典相关函数: 1.字典定义与访问 1.1 直接定义字典: stocks = {'000001':'平安银行', '000002':'万科A'