【Python随笔】__init__与__new__

本文详细探讨了Python中的`__init__`和`__new__`两个特殊方法,它们在对象生命周期中扮演关键角色。`__init__`用于对象初始化,而`__new__`则负责对象的创建。通过实例展示了两者如何协同工作,以及何时需要重写`__new__`,如在实现单例模式或定制不可变类型时。同时,文中还分析了不使用`__init__`的潜在问题,并给出了对比不同初始化方式的效果。
摘要由CSDN通过智能技术生成

【Python随笔】__init____new__

关于这两个魔法函数的含义在秋招时常被问到,为了弄清本质进行了一些挖掘与整理,关于__init____new__方法的区别可见文末总结

__init__()方法

在创建类时,我们可以手动添加__init__这一特殊的类实例方法,称为构造方法

构造方法是在创建一个类对象之后立即调用的方法,通常用于一个类实例的初始化,比如赋值给对象属性等

语法格式:

def __init__(self,...):
    code_block

【注】

  1. __init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身

  2. 有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去

  3. 即便不手动为类添加任何构造方法,Python 也会自动为类添加一个仅包含 self 参数的构造方法,又称为类的默认构造方法

  4. 与C#、java中的构造函数有些许不同,执行时实例已经被构造出来,其作用只是初始化已实例化后的对象

为什么要使用__init__()方法?

__init__()方法有两个方面的重大意义:

  1. 对象生命周期的基础是创建、初始化和销毁,每个对象必须正确初始化后才能正常工作
  2. __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'>

可以看出:

  1. __new__()方法是在生成对象之前所做的动作,接受的参数是cls类
  2. 此时只有__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

整个过程执行逻辑如下:

  1. 首先执行Student类的__new__()方法,该方法接收Student类作为参数,返回一个实例
  2. __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__方法的区别

  1. __new__是一个静态方法,而__init__是一个实例方法

  2. __new__用于控制生成一个新实例的过程,它是类级别的方法,在实例创建过程中最先被调用

  3. __init__用于初始化一个新实例,它是实例级别的方法,只有在__new__返回一个cls的实例时,后面的__init__才能被调用

  4. __new__必须要有返回值,返回实例化出来的对象,__init__可以没有返回值

参考:
Codevoila
廖雪峰的官方网站
Python笔记 class中的__init__()方法
详细解读Python中的__init__()方法
python的__new__方法
python中的__new__方法与单例模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值