@[TOC](python call(可调用对象理解))
前言
在Python中,方法也是一种高等的对象(python:万物皆对象)。我们平时自定义的函数、内置函数和类都属于可调用对象,但凡是可以把一对括号()应用到某个对象身上都可称之为可调用对象,判断对象是否为可调用对象可以用函数 callable。如果在类中实现了 __call__
方法,那么实例对象也将成为一个可调用对象。我们可以自定义可调用对象,比如pytorch中的nn.Module
类就是利用了__call__
实现了实例的可调用。
model=nn.Module()
model(x)
实际上在Module里实现了__call__
,并调用了forward函数,进行前向传播。
具体说明
允许一个类的实例像函数一样被调用。实质上说,这意味着 x()
与x.__call__()
是相同的。注意 __call__
参数可变。这意味着你可以定义__call__
为其他你想要的函数,无论有多少个参数。__call__
在那些类的实例经常改变状态的时候会非常有效。调用这个实例是一种改变这个对象状态的直接和优雅的做法。用一个实例来表达最好不过了:
class Entity(object):
'''调用实体来改变实体的位置。'''
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)
实际上,每一个可调用对象都有一个__call__方法。比如我随便定义一个函数,然后通过两种方式来调用它:
def print_msg(msg):
print(msg)
print_msg.__call__('hello')
print('hello')
结果是一样的。
同时,用 call() 弥补 hasattr() 函数的短板。 hasattr() 函数的功能是查找类的实例对象中是否包含指定名称的属性或者方法,但该函数有一个缺陷,即它无法判断该指定的名称,到底是类属性还是类方法。
要解决这个问题,我们可以借助可调用对象的概念。要知道,类实例对象包含的方法,其实也属于可调用对象,但类属性却不是。举个例子:
class CLanguage:
def __init__ (self):
self.name = "C语言中文网"
self.add = "http://c.biancheng.net"
def say(self):
print("我正在学Python")
clangs = CLanguage()
if hasattr(clangs,"name"):
print(hasattr(clangs.name,"__call__"))
print("**********")
if hasattr(clangs,"say"):
print(hasattr(clangs.say,"__call__"))
程序执行结果为:
False
**********
True
可以看到,由于 name 是类属性,它没有以 call 为名的 call() 方法;而 say 是类方法,它是可调用对象,因此它有 call() 方法。
另外,我们可以通过__call__
中调用我们想要调用的方法。参数传递可以利用*args
和**kwards
。
class Entity(object):
'''调用实体来改变实体的位置。'''
def __init__(self, x,y):
self.x,self.y=x,y
def __call__(self, *args,**kwargs):
'''改变实体的位置'''
self.change_loc(*args,**kwargs)
def change_loc(self,*args,**kwargs):
self.x,self.y=args[0],args[1]
print(self.x,self.y)
e = Entity(1, 2)
e(4, 5)