前言
当我们自定义类时,常见的情况是,只定义了__init__方法对进行类实例初始化的工作,似乎不涉及其他语言比如Java中常说的“需要对象就new一个”的操作,但Python的 新式类 中也有__new__方法,还有__call__方法的概念。
文章目录
三类方法介绍
1、__init__方法(初始化方法)
init 是 initialization(初始化)的缩写,__init__方法是类的一个特殊的固有方法,通常称为 初始化方法,用于初始化对象,定义并初始化对象的属性,即给属性赋值 。格式形如:
def __init__(self, arg1, arg2, ..., argn):
self.arg1 = arg1
self.arg2 = arg2
...
self.argn = argn
__init__ 方法的第一个参数是self( self 通常由 __new__ 方法返回),指代当前实例,而不一定是该类的实例;arg1, arg2, …, argn为形参,在实例化时需传递对应的实参,一般用于初始化实例属性。
class Demo:
def __init__(self, name, age):
self.name = name
self.age = age
if __name__ == '__main__':
obj = Demo('sidian', 18) # 创建Demo的实例obj,初始化name为'sidian',age为18
print(obj.name)
print(obj.age)
# 输出
# sidian
# 18
2. __new__方法 (构造方法)
__new__方法就是 创建实例的静态方法,在类准备实例化时调用,其作用是为实例对象分配内存空间、返回对象的引用传递给 __init__ 方法的 self 参数,因此调用顺序优先于 __init__ 初始化方法。
在自定义新式类时,__new__方法可定义可不定义,当没有重新定义__new__()时,Python默认调用该类直接父类的__new__()方法来构造该类的实例,如果该类的父类也没有重写__new__(),那么将一直向上追溯父类的__new__()方法直至object类(所有新式类的基类)的__new__()方法。
在基类object中默认的__new__方法已经封装了创建对象、为对象分配空间的动作。
object类中__new__方法源代码如下:@staticmethod # known case of __new__ def __new__(cls, *more): # known special case of object.__new__ """ Create and return a new object. See help(type) for accurate signature. """ pass
当在类中重写__new__方法时,无论有没有被加上@staticmethod
装饰器,它始终都是类的静态方法,格式形如:
def __new__(cls, *args, **kwargs):
...
return 实例对象
参数:__new__方法至少要有第一个参数cls,代表当前要实例化的类,此参数在实例化时由Python解释器自动提供;位置参数和关键字参数用于接收实例化时输入的实参。
返回值:__new__方法还必须要有返回值,返回一个实例化对象(通常会返回该类的实例,但也可能返回其他类的实例),然后传入被返回实例对象所在类的__init__初始化方法的第一个参数。
验证
class Cir(): def __new__(cls, *args, **kwargs): # 看看Python传递给__new__的参数 print("Python传递给__new__的参数:\ncls: ", cls, "\nargs: ", args, "\nkwargs:", kwargs) inst = super().__new__(cls) print("__new__返回值:", inst) return inst def __init__(self, radius, size): print("\nPython传递给_init__的参数:\nself的值为:", self, "\nradius的值为:", radius, "\nsize的值为:", size) self.radius = radius cir = Cir(10, size=8)
运行结果:
Python传递给__new__的参数: cls: <class '__main__.Cir'> args: (10,) kwargs: {'size': 8} __new__返回值: <__main__.Cir object at 0x0000021411631EB0> Python传递给_init__的参数: self的值为: <__main__.Cir object at 0x0000021411631EB0> radius的值为: 10 size的值为: 8
可以看到,__new__方法中调用父类__new__方法返回的实例 inst 与__init__方法的 self 指向同一个内存地址;位置参数和关键字参数接收创建实例传入的所有位置参数和关键字参数。
2.1 __new__方法必须要返回实例
如下例,自定义类B,只定义__init__方法,对B进行实例化,观察结果:
class B(object):
def __init__(self):
print("__init__被执行了!")
b = B()
print(b)
# 执行结果:
# __init__被执行了!
# <__main__.B object at 0x0000025AAE3CD730>
对类B加上没有任何返回结果的__new__方法,再进行实例化,观察结果:
class B(object):
def __new__(cls):
print("__new__被执行了!")
def __init__(self):
print("__init__被执行了!")
b = B()
print(b)
# 执行结果:
# __new__被执行了!
# None
这次执行结果没有像一开始只有__init__方法那样输出实例的内存地址,因为如上述所说,__init__ 方法的 self 形参的实参由__new__方法返回并传递——只有__init__方法的类B继承了object的__new__方法返回了实例,因此能够顺利创建并初始化实例;而有无返回实例__new__方法的类B无法传递实例,因此无法成功执行初始化__init__方法。
如果要得到当前类的实例,则需要在当前类中的__new__方法语句中调用当前类的父类的__new__方法;如果当前类是直接继承自object,则可以直接写成return object.__new__(cls)
。
将__new__方法修改为返回类B实例的方法,进行实例化,观察结果:
class B(object):
def __new__(cls):
print("__new__被执行了!")
return super().__new__(cls) # 或 object.__new__(cls)
def __init__(self):
print("__init__被执行了!")
b = B()
print(b)
# 执行结果:
# __new__被执行了!
# __init__被执行了!
# <__main__.B object at 0x00000262A8E6ABB0>
2.2 __new__方法不一定返回当前类实例
通常,当前类开始实例化时,__new__方法会返回cls(cls指代当前类)的实例;该类的__init__方法作为构造方法会接收这个实例(即self)作为自己的第一个参数,再依次传入__new__方法中接收的位置参数和命名参数。如果__new__方法返回其他类的实例,那么只会调用被返回类的构造方法。 如下例:
class Foo(object):
def __init__(self, *args, **kwargs):
pass
def __new__(cls, *args, **kwargs):
return object.__new__(Stranger)
class Stranger(object):
pass
foo = Foo()
print(type(foo))
# 执行结果:<class ‘main.Stranger'>
2.3 __new__方法接收实例化时参数的意义
思考:由上述可知,__new__方法的固定语法参数格式为 (cls, *args, **kwargs)
。其中*args, **kwargs
接收的是实例化时的参数,那么__new__方法接收这些参数的意义是什么?该方法返回值的实例对象所需,还是可用这些参数按照需求实现某些操作?
2.3.1 探索一:参数是否是返回值的实例对象所需
情况①:父类为object【留个坑】
假设某类父类默认为object,直接用object.__new__
方法返回实例对象,所有参数传入此方法中。
Python帮助文档 表明可以传多个值!如下所示:
object.__new__(cls[, …])
调用以创建一个 cls 类的新实例。__new__() 是一个静态方法 (因为是特例所以你不需要显式地声明),它会将所请求实例所属的类作为第一个参数。其余的参数会被传递给对象构造器表达式 (对类的调用)。__new__() 的返回值应为新对象实例 (通常是 cls 的实例)。
Typical implementations create a new instance of the class by invoking the superclass’s __new__() method using super().__new__(cls[, …]) with appropriate arguments and then modifying the newly created instance as necessary before returning it.
如果 __new__() 在构造对象期间被发起调用并且它返回了一个 cls 的实例,则新实例的 __init__() 方法将以 __init__(self[, …]) 的形式被发起调用,其中 self 为新实例而其余的参数与被传给对象构造器的参数相同。
如果 __new__() 未返回一个 cls 的实例,则新实例的 __init__() 方法就不会被执行。
__new__() 的目的主要是允许不可变类型的子类 (例如 int, str 或 tuple) 定制实例创建过程。它也常会在自定义元类中被重载以便定制类创建过程。
交互模式下通过help查看此方法显示可传多个参数
>>> help(object.__new__) Help on built-in function __new__: __new__(*args, **kwargs) method of builtins.type instance Create and return a new object. See help(type) for accurate signature.
示例探索如下:
class Cir():
def __new__(cls, *args, **kwargs):
return object.__new__(cls, *args, **kwargs)
def __init__(self, radius):
print("\nPython传递给_init__的参数:\nself的值为:", self, "\nradius的值为:", radius)
self.radius = radius
cir = Cir(10)
执行结果如下:
Traceback (most recent call last): TypeError: object.__new__() takes exactly one argument (the type to instantiate)
该报错显示,object.__new__只能接收一个参数且只能接收类名,即必须固定语法格式为 object.__new__(cls)
。
至于为何object.__new__方法定义可接收多个参数实际却只能接收cls,不太清楚,留个坑!
情况②:父类为自定义类
定义两个类,Vehicle类和Car类,Car是从Vehicle派生的,Car重写了__new__方法,验证几种情况:
- Vehicle类没有重写__new__方法。
- Vehicle类重写__new__方法,该方法只接受一个参数cls。
- Vehicle类重写__new__方法,该方法接受所有参数。
分析: ● 对于1,因为Vehicle没有重写__new__方法,最终会调用基类 object的__new__方法,执行结果与上述情况①一致,不再赘述。 ● 对于2,因为Vehicle类的__new__方法只接受cls一个参数,所以定义Vehicle类的__init__方法时也只能有一个参数self而不能有其他实例变量。另外,当Car类的__new__方法需要调用父类的__new__方法传入全部参数时欲返回实例时必然行不通,只能传入参数cls进行验证。 ● 对于2和3,Vehicle类的父类为 object,因此Vehicle类中的 __new__方法返回实例时只能写成super().__new__(cls)或 object.__new__(cls) |
验证2:Vehicle类重写__new__方法,该方法只接受一个参数cls,示例如下:
class Verhicle():
def __init__(self):
print("__init__被执行了!")
def __new__(cls):
print("In Verhicle __new__,Python传递给__new__的参数:\ncls:", cls)
inst = super().__new__(cls)
print("__new__的返回值:", inst)
return inst
class Car(Verhicle):
def __init__(self, wheelcount, power, oilcostperkm):
self.wheelcount, self.power, self.oilcostperkm = wheelcount, power, oilcostperkm
print("__init__的实例变量:", self.wheelcount, self.power, self.oilcostperkm)
def __new__(cls, *args, **kwargs):
print("In Car __new__,Python传递给__new__的参数:\ncls:", cls, "\nargs:", args, "\nkwargs:", kwargs)
inst = super().__new__(cls)
print("__new__的返回值:", inst)
return inst
car = Car(4, "汽车发动机", 0.1)
运行结果:
In Car __new__,Python传递给__new__的参数:
cls: <class '__main__.Car'>
args: (4, '汽车发动机', 0.1)
kwargs: {}
In Verhicle __new__,Python传递给__new__的参数:
cls: <class '__main__.Car'>
__new__的返回值: <__main__.Car object at 0x0000026098C32D60>
__new__的返回值: <__main__.Car object at 0x0000026098C32D60>
__init__的实例变量: 4 汽车发动机 0.1
执行成功!结果表明,Vehicle类的__new__方法没有定义*args
, **kwargs
两类参数也能顺利返回实例,子类Car也能顺利进行实例初始化。
验证3:Vehicle类重写__new__方法,该方法接受所有参数的情况,示例如下:
class Verhicle():
def __init__(self, wheelcount, power):
self.wheelcount, self.power, self.totaldistance = wheelcount, power, 0
def __new__(cls, *args, **kwargs):
print("In Verhicle __new__,Python传递给__new__的参数:\ncls:", cls, "\nargs:", args, "\nkwargs:", kwargs)
inst = super().__new__(cls)
print("__new__的返回值:", inst)
return inst
class Car(Verhicle):
def __init__(self, wheelcount, power, oilcostperkm):
self.oilcostperkm = oilcostperkm
super().__init__(wheelcount, power)
def __new__(cls, *args, **kwargs):
print("In Car __new__,Python传递给__new__的参数:\ncls:", cls, "\nargs:", args, "\nkwargs:", kwargs)
inst = super().__new__(cls, *args, **kwargs)
print("__new__的返回值:", inst)
return inst
car = Car(4, "汽车发动机", 0.1)
运行结果如下:
In Car __new__,Python传递给__new__的参数:
cls: <class '__main__.Car'>
args: (4, '汽车发动机', 0.1)
kwargs: {}
In Verhicle __new__,Python传递给__new__的参数:
cls: <class '__main__.Car'>
args: (4, '汽车发动机', 0.1)
kwargs: {}
__new__的返回值: <__main__.Car object at 0x0000024A15352D90>
__new__的返回值: <__main__.Car object at 0x0000024A15352D90>
执行成功!虽然Vehicle类__new__方法可接受所有参数,但是*args, **kwargs
是否就是该方法返回实例所必需的变量?另外根据Vehicle类__new__方法的定义,该方法也可只接受第一个参数cls。下面探究Car类__new__方法调用父类__new__方法时只传入cls。
class Verhicle():
def __init__(self, wheelcount, power):
self.wheelcount, self.power, self.totaldistance = wheelcount, power, 0
def __new__(cls, *args, **kwargs):
print("In Verhicle __new__,Python传递给__new__的参数:\ncls:", cls, "\nargs:", args, "\nkwargs:", kwargs)
inst = super().__new__(cls)
print("__new__的返回值:", inst)
return inst
class Car(Verhicle):
def __init__(self, wheelcount, power, oilcostperkm):
self.oilcostperkm = oilcostperkm
super().__init__(wheelcount, power)
def __new__(cls, *args, **kwargs):
print("In Car __new__,Python传递给__new__的参数:\ncls:", cls, "\nargs:", args, "\nkwargs:", kwargs)
inst = super().__new__(cls)
print("__new__的返回值:", inst)
return inst
car = Car(4, "汽车发动机", 0.1)
运行结果:
In Car __new__,Python传递给__new__的参数:
cls: <class '__main__.Car'>
args: (4, '汽车发动机', 0.1)
kwargs: {}
In Verhicle __new__,Python传递给__new__的参数:
cls: <class '__main__.Car'>
args: ()
kwargs: {}
__new__的返回值: <__main__.Car object at 0x000001BA42AE2D90>
__new__的返回值: <__main__.Car object at 0x000001BA42AE2D90>
执行成功!结果表明,即使没给Vehicle类的__new__方法传入*args
, **kwargs
两类参数也能顺利返回实例,子类Car也能顺利进行实例初始化。
结论:
1. 情况①和情况②均表明,子类接收实例化时参数的*args, **kwargs
不是其__new__方法调用父类__new__方法获取实例对象所必需的。
2. 情况①中表明,调用object. __ new__方法时,只能传cls参数。
3. 情况②中表明,子类调用自定义父类的__ new__方法时,可否传入所有参数,取决于父类的__ new__方法的定义。
2.3.2 探索二:可用这些参数按照需求实现某些操作
结合上述 2.2 __new__方法不一定返回当前类实例 的特点,举例体现__new__方法接收实例化时参数的意义。
● 需求一:修改Python内置类型 int 的创建行为,只允许创建非0整数。
实现方式:由于 int 可创建整数,但其包括0且已是不可变类型,所以尝试直接通过自定义子类继承 int并附加某些判断来实现。
class nonZero(int):
def __new__(cls,value):
return super().__new__(cls,value) if value != 0 else None
def __init__(self,skipped_value):
print("__init__()")
super().__init__()
print(type(nonZero(-12)))
print(type(nonZero(0)))
运行结果:
__init__()
<class '__main__.nonZero'>
<class 'NoneType'>
可以看到,nonZero的__new__方法接收实例参数后,可依据实例参数所传值的合理性来控制 int 的生成。这为修改不可变类的内置类型(如 int、str、float 等)的创建行为的需求,提供了思路!
● 需求二:实例化人时,只允许传入合理的年龄值。
实现方式:通过类的__new__方法对传入实例参数进行判断,控制实例的生成。
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
def __new__(cls, name, age):
if 0 < age < 150:
return object.__new__(cls)
# return super(Person, cls).__new__(cls)
else:
return None
def __str__(self):
return '{0}({1})'.format(self.__class__.__name__, self.__dict__)
print(Person('Tom', 10))
print(Person('Mike', 200))
运行结果:
Person({'age': 10, 'name': 'Tom'})
None
可以看到,200岁的初始值时不合理的,因此实例也不会被生成。
结论:需求一和需求二均体现了__new__方法接收实例化时参数的可对参数进行判断,进而生成符合需求的实例的作用。
3. __call__方法
Python中那些能够在后面加
()
即对象名()
来调用执行的对象,被称为 可调用对象,可调用对象包括 自定义函数、Python内置函数、实例对象和实例方法等。
__call__方法是Python中一个很特殊的方法。调用 可调用对象时,既可通过 对象名()
,也可通过 对象名.__call__()
来实现,两者之间的关系可理解为 对象名()
是 对象名.__call__()
的简写。特别地,当类中定义了__call__方法, 该类的实例对象 将成为可调用对象,调用该实例对象时执行__call__方法中的代码。
__call__方法的语法格式:__call__(self[, args...])
,第一个参数对对象本身。
⭕ 自定义函数
对自定义函数的调用,通常情况下是在函数名后加()来调用,也可以用__call__()方法来调用。
def test():
print("Function test() is called")
test()
test.__call__()
运行结果:
Function test() is called
Function test() is called
⭕ Python内置函数
与自定义函数一样,内置函数既可以通过函数名后加()来调用,也可以用__call__()方法来调用。
print(int(3))
print(int.__call__(3))
运行结果:
3
3
⭕ 类的实例对象
如果一个类中没有定义__call__()方法,那么这个类的实例对象是不可以被调用的。定义了__call__()方法后,调用该实例对象就是执行__call__()方法中的代码。
class Person(object):
def __call__(self):
print("Method __call__() is called")
d = Person()
d()
运行结果:
Method __call__() is called
3.1 判断是否可调用对象:callable函数
Python callable() 函数用于检查一个对象是否是可调用的。如果返回 True,调用对象object 仍然可能调用失败;但如果返回 False,调用对象object 绝对不会成功。
语法 :
callable(object)
参数 :对象(object)
返回值 :可调用返回 True,否则返回 False。
示例:
>>>callable(0)
False
>>> callable("school")
False
>>> def add(a, b):
... return a + b
...
>>> callable(add) # 函数返回 True
True
>>> class A: # 类
... def method(self):
... return 0
...
>>> callable(A) # 类返回 True
True
>>> a = A()
>>> callable(a) # 没有实现 __call__, 返回 False
False
>>> class B:
... def __call__(self):
... return 0
...
>>> callable(B)
True
>>> b = B()
>>> callable(b) # 实现 __call__, 返回 True
True
3.2 __call__的应用场景
3.2.1 类装饰器:将类的实例对象伪装成函数
基于类实现装饰器,就必须用到__call__方法。
class SafeAdd:
def __init__(self, func):
self.func = func # func是被装饰的函数
def __call__(self, *args, **kwargs):
if len(args) == 2:
left = args[0]
right = args[1] # 获取参数并进行类型转换
if not isinstance(left, (int, float)):
left = float(left)
if not isinstance(right, (int, float)):
right = float(right)
return left + right
return None
if __name__ == '__main__':
@SafeAdd
def add(a, b):
return a + b
print(add(3, 5)) # 输出8
print(add('3', '6')) # 输出9
以上执行原理相当于
def add(a, b):
return a + b
safe_add = SafeAdd(add) # add函数作为参数传入SafeAdd的初始化函数__init__中
# safe_add是类SafeAdd的实例
print(safe_add(3, 5))
print(safe_add('3', '6'))
实例对象safe_add所属的类由于实现了__call__方法,因此可以像使用函数一样通过一对小括号来调用执行。
3.2.2 简化类的对象的方法调用
当类对象某方法的调用频率很高时,则可将该方法命名为__call__来简化调用。
定义一个制作蛋糕类以及和面、发酵、烘烤、切形状、抹奶油、摆水果、打包七个步骤的实例方法,每实例化一个类时都要依次实现这些方法,如下所示:
class MakeCake:
#和面
def huomian(self):
print('和面操作')
#发酵
def fajiao(self):
print('发酵操作')
#烘烤
def hongkao(self):
print('烘烤操作')
#切形状
def qiexingzhuang(self):
print('切形状操作')
#抹奶油
def monaiyou(self):
print('抹奶油操作')
#摆水果
def baishuiguo(self):
print('摆水果操作')
#打包
def dabao(self):
print('打包操作')
#制作第一个蛋糕
cake1 = MakeCake() #第一个制作蛋糕的对象
cake1.huomian()
cake1.fajiao()
cake1.hongkao()
cake1.qiexingzhuang()
cake1.monaiyou()
cake1.baishuiguo()
cake1.dabao()
#制作第二个蛋糕
cake2 = MakeCake() #第二个制作蛋糕的对象
cake2 .huomian()
cake2 .fajiao()
cake2 .hongkao()
cake2 .qiexingzhuang()
cake2 .monaiyou()
cake2 .baishuiguo()
cake2 .dabao()
#制作第三个蛋糕
cake3 = MakeCake() #第三个制作蛋糕的对象
...
...
采用定义__call__方法的方式,封装这七个方法们可以大大简化制作蛋糕方法的调用。如下所示:
class MakeCake:
#和面
def huomian(self):
print('和面操作')
#发酵
def fajiao(self):
print('发酵操作')
#烘烤
def hongkao(self):
print('烘烤操作')
#切形状
def qiexingzhuang(self):
print('切形状操作')
#抹奶油
def monaiyou(self):
print('抹奶油操作')
#摆水果
def baishuiguo(self):
print('摆水果操作')
#打包
def dabao(self):
print('打包操作')
def __call__(self):
self .huomian()
self .fajiao()
self .hongkao()
self .qiexingzhuang()
self .monaiyou()
self .baishuiguo()
self .dabao()
cake1 = MakeCake() #第一个制作蛋糕的对象
cake1()
cake2 = MakeCake() #第二个制作蛋糕的对象
cake2 ()
cake3 = MakeCake() #第三个制作蛋糕的对象
cake3 ()
3.2.3 模糊对象和函数调用时的区别 (提高代码兼容性)
创建类A和函数B,类A下的m函数与函数B功能类似;现需将A的对象和B函数作为参数传到函数C中去执行,如下所示:
class A():
def m(self):
print('good')
a = A()
def B():
print('good')
def C1(func):
func.m()
def C2(func):
func()
C1(a)
C2(B)
因为对象和函数调用上的区别,所以要有两个不同的C函数才能实现相同的功能。现在通过__call__方法模糊两者的差别,提高程序的兼容性。
class A():
def __call__(self):
print('good')
a = A()
def B():
print('good')
def C(func):
func()
C(a)
C(B)
三者关系概述
- __new__ (构造方法): 实例对象的创建,是一个静态方法,第一个参数是cls。
- __init__ (初始化方法): 实例对象的初始化, 是一个实例方法,第一个参数是self。
- __call__ : 对象可调用,注意不是类调用,是对象调用。
思考
实例化一个类时,为什么总遵循先执行__new__方法,后执行__init__方法的顺序?
答疑:类实例化时先执行__new__后执行__init__的原理
参考
第8.6节 Python类中的__new__方法深入剖析:调用父类__new__方法参数的困惑
Python中new方法的详解
Python __new__()方法详解
Python 中__new__方法详解及使用
python中的__new__方法
详解Python的__call__()方法
python面向对象 __call__方法
Python中__call__的用法