【Python随笔】__init__
与__new__
目录
关于这两个魔法函数的含义在秋招时常被问到,为了弄清本质进行了一些挖掘与整理,关于__init__
和__new__
方法的区别可见文末总结
__init__()
方法
在创建类时,我们可以手动添加__init__
这一特殊的类实例方法,称为构造方法
构造方法是在创建一个类对象之后立即调用的方法,通常用于一个类实例的初始化,比如赋值给对象属性等
语法格式:
def __init__(self,...):
code_block
【注】
-
__init__
方法的第一个参数永远是self
,表示创建的实例本身,因此,在__init__
方法内部,就可以把各种属性绑定到self
,因为self
就指向创建的实例本身 -
有了
__init__
方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__
方法匹配的参数,但self
不需要传,Python解释器自己会把实例变量传进去 -
即便不手动为类添加任何构造方法,Python 也会自动为类添加一个仅包含 self 参数的构造方法,又称为类的默认构造方法
-
与C#、java中的构造函数有些许不同,执行时实例已经被构造出来,其作用只是初始化已实例化后的对象
为什么要使用__init__()
方法?
__init__()
方法有两个方面的重大意义:
- 对象生命周期的基础是创建、初始化和销毁,每个对象必须正确初始化后才能正常工作
__init__()
参数值可以有多种形式
我们首先尝试不使用__init__()
方法定义类:
class Student:
def print_info(self, name, age):
print("name: {}, age: {}".format(name, age))
student = Student()
student.print_info("Zola",23)
# __dict__也是类中定义的魔术方法,作用是把类中的私有属性装在一个字典里面返回出来
print(student.__dict__)
运行结果如下:
name: Zola, age: 23
{}
可以看到,虽然在不使用__init__()
方法也能正常实现要求,但查看这个实例的属性竟然是空的,不符合常理
在实例化对象时参数为空,只有在调用函数的时候才指定,这会导致这些定义的方法都带有参数,不够便捷
使用__init__()
方法定义类:
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def print_info(self):
print("name: {}, age: {}".format(self.name, self.age))
student = Student("Zola", 23)
student.print_info()
print(student.__dict__)
运行结果如下:
name: Zola, age: 23
{'name': 'Zola', 'age': 23}
在实例构造时就绑定了属性,即符合对象生命周期,又方便了类中其他方法的定义
__init__(self)
和__init(self,args)__
区别
第一种定义方式如下:
class Student:
def __init__(self):
self.name=None
self.gender=None
self.age=None
def print_info(self):
if str.lower(self.gender)=='female':
print('Her name is {}, she is {} years old'.format(self.name,self.age))
elif str.lower(self.gender)=='male':
print('His name is {}, he is {} years old'.format(self.name,self.age))
else:
print('ERROR')
student=Student()
student.name='Zola'
student.gender='female'
student.age=23
student.print_info()
这种方式下__init__()
方法中只有self,但在方法的类部包含三个属性定义,相当于一个空结构,当有输入进来的时候再添加相应的数据
第二种方式如下:
class Student:
def __init__(self, name, gender, age):
self.name = name
self.gender = gender
self.age = age
def print_info(self):
if str.lower(self.gender)=='female':
print('Her name is {}, she is {} years old'.format(self.name,self.age))
elif str.lower(self.gender)=='male':
print('His name is {}, he is {} years old'.format(self.name,self.age))
else:
print('ERROR')
student = Student('Zola', 'female', 23)
student.print_info()
在定义方法的时候直接给定了参数,则在实例化时则必须传值,否则会报错
__new__()
方法
在介绍__init__()
方法时我们强调,__init__()
方法并不能够创建实例,而是初始化已实例化后的对象
真正能够创建类实例的静态方法是__new__()
,它优先于 __init__()
初始化方法被调用
也就是说,__new__()
和__init__()
相配合才是python中真正的类构造器
为了更好地理解实例构建的过程,我们先进行一次不完整的尝试,在类中直接添加__new__()
方法,其中只进行信息打印:
class Student:
def __new__(cls, *args, **kwargs):
print("__new__ 方法的传入参数:%s" %cls)
print ('__new__ 方法被执行.')
def __init__(self, name, age):
print("__init__ 方法的传入参数:%s" %self)
print ('__init__ 方法被执行.')
self.name = name
self.age = age
def print_info(self):
print("name: {}, age: {}".format(self.name, self.age))
student = Student('zola', 23)
print(type(student))
# student.print_info()
运行结果如下:
__new__ 方法的传入参数:<class '__main__.Student'>
__new__ 方法被执行.
<class 'NoneType'>
可以看出:
__new__()
方法是在生成对象之前所做的动作,接受的参数是cls类- 此时只有
__new__()
方法被调用,但student的类型为'NoneType'
,并不是一个对象,因此__init__()
方法也并没有被调用,如果此时强行使用类函数student.print_info()
,程序将会报错
这是因为,对象的创建本质上是在__new__()
方法中进行return(返回一个对象),之前的代码中没有这一返回值,因此并没有对象生成,也就无法使用初始化方法__init__()
方法
__new__()
中返回了什么?
每一个Python类都隐含了一个超类:object,所有的类都是object子类,都继承了object这个父类的所有方法和属性
Python 中也隐含地提供了 __new__()
的默认实现,也就是说当类中没有重写__new__()
方法时,Python将调用其父类的__new__()
方法来构造该类的实例,如果父类也没有则一直向上追溯直到调用超类object的__new__()
方法,创建一个新的实例对象,然后返回它
以下两种定义是等价的:
# Python3 中的类都默认继承了object,因此无需显示指定
# Python2中类定义需写作class Animal(object):
class Animal:
def __new__(cls, *args, **kw):
return super().__new__(cls) # 重写了__new__方法,又调用了超类super().__new__()
def eat(self):
print('eat something!')
class Animal: # 没有重写__new__方法,python默认追溯调用基类的__new__()
def eat(self):
print('eat something!')
【注】在任何类的__new__()
方法中,都不能调用自身的__new__()
来制造实例,因为这会造成死循环
再将上例中的__new__()
方法补充完整:
class Student:
def __new__(cls, *args, **kwargs):
print("__new__ 方法的传入参数:%s" %cls)
print ('__new__ 方法被执行.')
return super().__new__(cls) # 调用了超类super().__new__()方法返回实例对象
def __init__(self, name, age):
print("__init__ 方法的传入参数:%s" %self)
print ('__init__ 方法被执行.')
self.name = name
self.age = age
def print_info(self):
print("name: {}, age: {}".format(self.name, self.age))
student = Student('zola', 23)
print(type(student))
student.print_info()
运行结果为:
__new__ 方法的传入参数:<class '__main__.Student'>
__new__ 方法被执行.
__init__ 方法的传入参数:<__main__.Student object at 0x00000165D0AE4C18>
__init__ 方法被执行.
<class '__main__.Student'>
name: zola, age: 23
整个过程执行逻辑如下:
- 首先执行Student类的
__new__()
方法,该方法接收Student类作为参数,返回一个实例 - 将
__new__()
返回的类实例作为self参数传递给__init__()
,当其存在时,将自动调用__init__()
完成对象的初始化工作
什么时候需要使用__new__
覆写不可变类型初始化
在大多数情况下我们都不需要重写__new__
方法,除非是在 __init__()
不能满足需要的时候
Python中的内置类int、str、tuple等属于不可变类型,一旦创建了这样不可变的对象实例,就无法在 __init__()
方法中对其进行修改,例如,我们想要定义一个正整数类型,该类型继承于int
类:
class PositiveInteger(int):
def __new__(cls, value):
# 必须在实例创建之前,也就是__new__中进行修改
return super().__new__(cls, abs(value))
def __init__(self, value):
# 这里__init__()不能传入任何参数,因为父类int中没有任何参数
super().__init__()
print(PositiveInteger(-3))
运行结果为:
3
实现python中的单例模式
另一种__new__
方法的应用是用来实现著名的设计模式之一,单例模式
首先看一下在不使用单例模式时,存在多个实例的情况:
class Student:
def __init__(self, name):
self.name = name
# 实例化三个对象
s1 = Student("Abby")
s2 = Student("Simon")
s3 = Student("Zola")
# 打印对象的地址
print(s1)
print(s2)
print(s3)
# 修改s1姓名,查看s2,s3
s1.name = "unkown"
print(s1.name)
print(s2.name)
print(s3.name)
运行结果如下:
<__main__.Student object at 0x0000028D34E9D190>
<__main__.Student object at 0x0000028D34F26640>
<__main__.Student object at 0x0000028D34F34E50>
unkown
Simon
Zola
可以看到每次实例化都开辟了一片新的内存空间,因此3个地址是不同的
在对其中一个实例属性进行修改后,其余实例不受影响,实例之间是完全独立而的
而单例模式的要求是:
对象只会被实例化一次,从第二次开始其实就是用的第一次实例化的对象,所有的实例化对象都公用一个内存地址,当一个对象改变某些属性或方法时,其他的对象也会跟着改变
class SingleStudent:
__isinstance = False # 设置一个私有变量,默认没有被实例化
# 在重写__new__方法时候用if语句进行判断
def __new__(cls, *args, **kwargs):
if cls.__isinstance: # 如果已经被实例化
return cls.__isinstance # 返回实例化对象
cls.__isinstance = object.__new__(cls) # 否则实例化
return cls.__isinstance # 返回实例化的对象
def __init__(self, name):
self.name = name
# 实例化三个对象
s1 = SingleStudent("Abby")
s2 = SingleStudent("Simon")
s3 = SingleStudent("Zola")
# 打印对象的地址
print(s1)
print(s2)
print(s3)
# 打印实例
print(s1.name)
print(s2.name)
print(s3.name)
# 给s2添加一个age属性,查看s1 s3
s2.age = 18
print(s1.age)
print(s2.age)
print(s3.age)
运行结果如下:
<__main__.SingleStudent object at 0x000002C7F90CD2E0>
<__main__.SingleStudent object at 0x000002C7F90CD2E0>
<__main__.SingleStudent object at 0x000002C7F90CD2E0>
Zola
Zola
Zola
18
18
18
可以看到三个对象使用了同一个地址,且属性值都是最新的,这样就成功地实现了单例设计模式
总结:__init__
和__new__
方法的区别
-
__new__
是一个静态方法,而__init__
是一个实例方法 -
__new__
用于控制生成一个新实例的过程,它是类级别的方法,在实例创建过程中最先被调用 -
__init__
用于初始化一个新实例,它是实例级别的方法,只有在__new__
返回一个cls的实例时,后面的__init__
才能被调用 -
__new__
必须要有返回值,返回实例化出来的对象,__init__
可以没有返回值
参考:
Codevoila
廖雪峰的官方网站
Python笔记 class中的__init__()方法
详细解读Python中的__init__()方法
python的__new__方法
python中的__new__方法与单例模式