前言Python中类构造函数的__new__方法有什么作用?Python类中的一些方法名和属性名前面有__双下划线。这类方法和属性通常属于Python的特殊方法和特殊属性。可以通过重写这些方法或直接调用这些方法来实现特殊功能。今天我们就来说说构造方法__new__实际程序的应用场景。我们知道常见的初始化__init__方法,可以通过重写实现我们想要的初始化逻辑。最近在实际业务开发过程中遇到了一类问题,比如数据资源加载和缓存机制的实现。使用的是魔术方法中的构造方法。其中,__init__()和__new__是对象构造函数。合理使用会有效提高程序性能。.希望大家深入理解,结合自身业务需求灵活运用,让代码更加优雅。一、__new__方法简介接下来,我们将通过实例一步步详细解释__new__方法在类初始化过程中到底是一个什么样的存在!1.初始化数据加载+解析类实例classSolution(object):def__init__(self,name=None,data=None):self.name=nameself.data=data#初始化加载数据self.xml_load(self.data)defxml_load(self,data):print("Initializeinit",data)defParser(self):print("parsefinish",self.name)a=Solution(name="A111",data=10)一个.Parser()b=Solution(name="A112",data=20)b.Parser()#print(a)和print(b)返回类名和对象地址print(a)print(b)#可以使用内置函数id()查看python对象的内存地址print(id(a))print(id(b))initializationinit10analysiscompletedfinishA111initializationinit20analysiscompletedfinishA112<__main__.Solutionobjectat0x0000024A3AF28D48><__main__.Solutionobjectat0x0000024A3B055C48>25178398098642517841042504注:1)、代码实例化类过程一般使用__init__()方法初始化类实例,在代码中实例化一个类时,冷杉st调用是执行__new__()方法,当定义类中没有重新定义__new__()方法时,Python会默认调用父类的__new__()方法构造实例。新的方法是先创建一个空间,然后每次创建一个实例化对象,然后使用开辟的空间来存放实例化对象;再次创建实例化对象时,使用new方法开辟空间存放实例化对象。请注意,只有从object继承的类才有此方法。2)、内存地址和对象可以相互转换#通过_ctypes的api,导入内存地址的对象_ctypesobj=_ctypes.PyObj_FromPtr(id(a))#打印出通过内存地址找到的对象print(obj)print(id(a))和print(id(b))打印出内存地址(十进制),print(a)和print(b)返回类的名称和对象的地址,但是两者不一样。每次实例化一个类时,都会创建并分配一个不同的对象地址。因此,代码实例化类时返回的类对象的地址引用也不一样。2.初始化数据加载重写新方法+解析类实例类解决方法:"""注:新方法是为实例化对象创建空间的方法,现在新方法重写了,不解释了将实例化对象引用返回给python控制器无法为实例化对象创建空间存储,所以运行代码会报错,初始化操作还没有完成。"""def__new__(cls,*args,**kwargs):print("对象创建空间")cls.instance=super().__new__(cls)print(cls.instance)#returncls.instance#Ifnot返回实例对象引用,实例化方法会报错:AttributeError:'NoneType'objecthasnoattribute'Parser'def__init__(self,name,data):self.name=nameself.data=dataself.xml_load(self.data)defxml_load(self,data):print("init",data)defParser(self):print("parsefinish",self.data)a=Solution("A111",10)a.Parser()print(id(a))注:1)、__init__()方法和__new__()方法的区别__new__()方法是用来创建实例的,在类被实例化之前会先被调用,它是类的一个方法,它是一个静态方法。__init__()方法用户对实例进行初始化,在创建实例对象后调用。它是实例对象的方法,用于设置类的一些初始值类实例对象。如果__init__()方法一个d__new__()方法,先调用__new__()方法,再调用__init__()方法。__new__()方法是创建实例的第一步。执行后需要返回创建类的实例,否则会报错,无法执行__init__()方法。其中,__init__()方法不会返回任何信息。2)覆盖__new__()方法def__new__(cls,*args,**kwargs):print(cls)#cls代表Solution类本身cls.instance=super().__new__(cls)#object().__new__()print(cls.instance)returncls.instancesuper()和object.__new__(cls)都是调用父类的new方法,父类必须new类的方法返回给函数开辟空间,所以必须加上return。代码的执行顺序是:先执行new方法,再执行init方法,最后执行其他方法。2.单例模式单例模式的最初定义出现在《设计模式》:“保证一个类只有一个实例,并提供一个全局访问点来访问它”。单例的使用主要是保证全局只有一个实例可以访问,比如系统日志的输出,操作系统的任务管理器等。1、新方法如何实现单例模式?class解决方案:#1.记录第一个创建对象的引用,代表类的私有属性_instance=None#类命名空间中存放静态变量def__init__(self,name,data):self.name=nameself.data=dataself.xml_load(self.data)def__new__(cls,*args,**kwargs):#2.判断该类的属性是否为空;如果第一个对象还没有创建,我们应该调用父类的方法为第一个对象分配空间ifcls._instance==None:#3.将class属性中保存的对象引用返回给python解释器cls._instance=object.__new__(cls)#3returncls._instance#如果cls._instance不为None,直接返回实例化的实例对象else:returncls._instance#new方法必须返回地址,这样它有存储空间defxml_load(self,data):print("init",self.name,data)defParser(self):print("parsefinish",self.name)a=Solution("A11",10)#第一次开辟一个对象空间地址,后续创建都在这个地址上进行a.Parser()b=Solution("A12",20)#b覆盖ab.Parser()print(id(a))print(id(b))#内存地址,它们的内存地址相同print(a.name)print(b.name)initializationinitA1110analysiscompletedfinishA11initializationinitA1210analysiscompleted完成A1224651401998162465140199816A12A12注:1),单例模式总是只有一个空间,这个空间被复用了。首先定义一个类的私有属性_instance,用来记录第一个创建对象的引用。如果cls._instance为None,则表示该类还没有被实例化如果通过,则实例化该类并返回实例对象。通过下面的数据测试,可以知道print(obj.name,obj.data)最终打印的是A12。第一次打印“A11”时,属性为空,执行if语句开辟空间存放属性;从第一个空间已经被第二个敲入开辟出来,执行else语句,直接将“A12”返回到原来的空间,覆盖之前的覆盖数据。deftask(id,data):obj=Solution("{0}".format(id),"{0}".format(data))print(obj.name,obj.data)importthreadingID=["A11","A12","A13","A14","A12"]DATA=[10,20,30,40,20]foriinrange(5):t=threading.Thread(target=task(ID[i],DATA[i]),args=[i,])t.start()<__main__.Solutionobjectat0x00000221B2129148>初始化initA1110A1110初始化initA1220A1220初始化initA1330A1330初始化initA1440A14in41220A12202)、单例模式的另一种实现方法def__new__(cls,*args,**kwargs):#hasattr查询目标,判断是否有,not1==1returnsFalse#aftertheifstatementwhentheoverall#not条件为True,执行cls.instance=object....代码#当if语句后的#not条件整体为False时,执行返回码ifnothasattr(cls,"instance"):#hasattrcheck,作用判断cls.instance=object.__new__(cls)returncls.instance2.如何控制类执行初始化我只有一次吗?上面实现了单例模式对象空间的复用,但是有时候我们希望在初始化过程中只加载一次,避免频繁请求浪费系统资源(比如数据库连接请求数据)。class解决方法:#定义类变量#记录第一个创建对象的引用,代表类的私有属性_instance=None#记录是否执行了初始化动作init_flag=Falsedef__init__(self,name,data):自己。name=nameself.data=data#使用类名调用类变量,不能直接访问。ifSolution.init_flag:returnself.xml_load(self.data)#修改类属性的标记Solution.init_flag=Truedef__new__(cls,*args,**kwargs):#判断类的属性是否为空;一个对象还没有创建,我们应该调用父类的方法为第一个对象分配空间ifcls._instance==None:#将类属性中保存的对象引用返回给python解释器cls._instance=object.__new__(cls)returncls._instance#如果cls._instance不为None,直接返回实例化的实例对象else:returncls._instancedefxml_load(self,data):print("Initializeinit",self.name,data)defParser(self):print("parsefinish",self.name)a=Solution("A11",10)#第一次实例化对象的地址,后续所有创建都在这个地址上进行a.Parser()b=Solution("A12",20)#b覆盖一个b.Parser()print(id(a))print(id(b))print(a.name)print(b.name)initializationinitA1110parsingcompletefinishA11parsingcompletefinishA1222808557203282280855720328A12A12注:1)在单例模式下,初始化过程只加载一次。这时我们在类空间中再增加一个init_flag属性来记录是否进行了初始化操作,实现加载初始化过程。从上面两个实例化过程的结果来看,对象引用地址不变,结果被上次实例化数据覆盖,初始化init只打印一次。2)单例模式下资源加载的注意点单例模式下,控制类只需要初始化一次,适用于一次性加载资源到缓存的过程,多实例模式可以用于多进程应用程序。3、多实例模式多个实例对象空间引用地址是完全独立的,以保证不同的请求资源不被占用。将对同一对象的请求分组到同一实例中。class解决方案:##定义类实例化对象字典,即不同的实例对象对应不同的对象空间地址引用_loaded={}def__init__(self,name,data):self.name=nameself.data=dataself.xml_load(self.data)def__new__(cls,name,*args):ifcls._loaded.get(name)isnotNone:client=cls._loaded.get(name)print(f"alreadyexistsaccessobject{name}")print(client)returnclient#将class属性中保存的对象引用返回给python解释器print(f"Creatingaccessobject{name}")client=super().__new__(cls)#是添加一个类实例名称的空间对象地址引用print(client)cls._loaded[name]=clientreturnclientdefxml_load(self,data):print("initializeinit",self.name,data)defParser(self):print("parsefinish",self.name)if__name__=='__main__':print("多实例模式实例")a=Solution("A11",10)a.Parser()b=Solution("A11",10)b.Parser()c=Solution("A12",20)c.Parser()print(f"{aisb}")print(a.name)print(b.name)print(c.name)注:1)、多实例模式总是有多个空格,不同空格完全独立让我们在类空间中定义类实例化对象字典,即创建不同的实例对象和对象空间地址引用键值对,从而实现多实例模式。使用类字典判断实例对象是否创建,节省创建成本。2)多实例模式测试流程当创建同一个实例对象name="A11"时,程序首先在实例池中查找cls._loaded.get(name),如果存在则直接返回创建的实例对象空间。多实例模式完美实现了不同访问对象的具体不同的实例化对象地址。多实例模式instanceiscreatingaccessobjectA11<__main__.Solutionobjectat0x000001C105AA5EC8>initializationinitA1110analysiscompletefinishA11alreadyexistsaccessobjectA11<__main__.Solutionobjectat0x000001C105AA5EC8>initializationinitA1110analysiscompletefinishA11正在创建访问对象A12<__main__.Solutionobjectat0x000001C105AA5F88>initializationinitA1220analysiscompletedfinishA12TrueA11A11A123),多实例模式下buffer机制的实现进一步优化了多实例模式的初始化流程,例如只加载一次初始化在读取文件或数据库时执行。class解决方案:##定义类实例化对象字典,即不同的实例对象对应不同的对象空间地址引用_loaded={}def__new__(cls,name,data,*args):ifcls._loaded.get(name)不是None:client=cls._loaded.get(name)print(f"访问对象{name}已经存在")print(client)returnclientprint(f"Creatingtheaccessobject{name}")#put将class属性中保存的对象引用返回给python解释器client=super().__new__(cls)print(client)#为类实例名添加一个空间对象地址引用cls._loaded[name]=clientclient._init_db(name,data)returnclientdef_init_db(self,name,data):self.name=nameself.data=dataself.xml_load(self.data)defxml_load(self,data):print("初始化初始化",self.name,data)defParser(self):print("parsefinish",self.name)if__name__=='__main__':print("Multipleinstancemodeinstance-cache")a=Solution("A11",10)a.Parser()b=Solution("A11",10)b.Parser()c=Solution("A12",20)c.Parser()print(f"{aisb}")print(a.name)print(b.name)print(c.name)multi-instancepatterninstanceisbeingcreatedaccessobjectA11<__main__.Solutionobjectat0x0000024198989148>初始化initA1110parsingcompletefinishA11alreadyexistsaccessobjectA11<__main__.Solutionobjectat0x0000024198989148>parsingcompletefinishA11正在创建AccessobjectA12<__main__.Solutionobjectat0x00000241989891C8>initializationinitA1220parsingcompletedfinishA12TrueA11A11A12注意:多实例模式下的多个实例化对象只初始化一次,创建后在__new__方法中重写每个实例对象初始化_init_db()方法执行一次,以后再遇到同一个实例对象什么都不会发生,直接返回创建的实例对象。从测试结果来看,当创建同一个实例对象name="A11"时,会跳过第二次初始数据加载过程,很好地实现了缓存机制。__new__方法总结本文结合项目背景详细介绍了__new__方法实现单例模式和多例模式以及缓存机制的实现!1.创建类的实例时自动调用__new__方法。2.实例是通过类中的__new__方法在类中创建的。3、首先调用__new__方法创建实例,然后调用__init__方法初始化实例。4.__new__方法,后面括号里的cls代表类本身。5.__new__方法,判断class属性为空则开辟空间,否则重新使用原地址。比较特殊的方法如1.自我描述方法:__repr__2.销毁方法:__del__3.列出对象的所有属性(包括方法):__dir__4.__dict__属性:查看所有属性名和属性值组成的字典在对象5里面,__getattr__\__setattr__等。当然还有metaclass类的__new__方法,可以动态修改程序中的一批类。这个功能在开发一些基础框架的时候非常有用。元类可用于将方法添加到需要通用功能的一批类中。后续有合适的示例场景分享。