对__call__的究极研究

1. __call__定义在普通类中

当在普通的类中定义__call__方法时,会使得实例对象变成一个可调用对象

python的可调用对象都会有__call__方法

class T:
    pass

t = T()
t()

输出

TypeError: 'T' object is not callable

说T的实例对象是不可调用的;但是当你在T中实现了__call__方法以后

class T:
    def __call__(self):
        print('__call__')

t = T()
t()  # __call__

实例对象t就变成了可调用对象。


通过上述例子,我们也可以得出一个结论,python中的可调用对象都会存在__call__方法。

比如函数:

def func():
    print('hello')

print(hasattr(func, '__call__'))  # True
func.__call__()  # hello

func()其实就相当于func.__call__()

hasattr()的作用是判断对象func是否含有方法或属性__call__
想了解hasattr()的可以看这篇文章:Python 的hasattr(), getattr() 和 setattr()函数

那么通过上面的例子,我们可以判断出func对象含有__call__方法,因此就可以断定他是一个可调用对象。

同时利用__call__也可以解决hasattr的一个缺点,hasattr仅能判断对象obj是否含有某个属性或方法,但是无法判断出其到底是属性还是方法。再利用__call__就可以判断出到底是属性还是方法了。

如下

class T:
    def __init__(self):
        self.name = 'Jack'

    def func(self):
        print('hello')

t = T()
print(hasattr(t, 'name'))  # True
print(hasattr(t.name, '__call__'))  # False

print(hasattr(t, 'func'))  # True
print(hasattr(t.func, '__call__'))  # True

通过如上的组合拳就能判断出t含有name和func,且name为属性,func为可调用对象(方法)


2. __call__在Metaclass中的作用

在实例化的时候,会调用Meatclass中的call方法,而Meatclass中的call方法又会调用类中的new和init去生成一个实例对象。

先把结论放在前面吧:
在实例化的过程中,会优先调用metaclass中的__call__,而这个__call__会先后默认调用类对象中的__new____init__,最终获得一个完整的实例对象。


2.1 证明

metaclass的作用以及__call__的调用顺序在之前的两篇文章中有研究过:
Python 元类
Python 中的 new
这里算是再来复习一下,写的就不会那么详细了。

首先要弄清楚类中__new____init__的作用:

  1. __new__ :类对象通过调用自己的__new__方法在内存中申请一个空间让实例对象居住。因此它需要接受一个参数cls用来明示是哪个类对象要申请空间,然后返回一个对应的实例对象。
  2. __init__ :当类对象为实例对象创建好空间以后,实例对象需要在调用__init__对这个空间进行初始化,因此__init__需要接受一个self参数,来告诉自己要实例化哪一个实例对象。

弄清楚以后看个例子

class Meta(type):
    def __call__(cls):
        print('Meta: __call__', cls)  # cls为类对象 T
        i = cls.__new__(cls)  # i 为实例对象 t, 此处调用了T的__new__方法,返回了t
        i.__init__()  # 调用了实例对象的__init__方法
        return i

class T(metaclass=Meta):
    def __new__(cls):
        print('T: __new__', cls)
        return super().__new__(cls)

    def __init__(self):
        print('T: __init__', self)
    

t = T()
# __new__的作用就是为实例对象创建空间,而且哪个类中的new创建的空间就由对应的实例对象居住
# 因此__new__接收的第一个参数就是类对象,而返回的结果是实例对象
# __init__的作用是初始化实例对象,因此接收的参数是实例对象

输出结果

Meta: __call__ <class '__main__.T'>
T: __new__ <class '__main__.T'>
T: __init__ <__main__.T object at 0x0000015C017513A0>

结果证明:在实例化的过程中,会优先调用metaclass中的__call__,而这个__call__会先后默认调用类对象中的__new____init__,最终获得一个完整的实例对象。


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值