详解Python中__new__方法的作用


Posted in Python onMarch 31, 2022

前言

Python中类的构造方法__new__方法有何作用?

Python类中有些方法名、属性名的前后都添加__双下画线,这种方法、属性通常属于Python的特殊方法和特殊属性。通过重写这些方法或直接调用这些方法来实现特殊功能。今天来聊聊构造方法__new__实际程序的应用场景。

我们知道常见的初始化__init__方法,可以重写实现自己想要的初始化逻辑。最近实际业务开发过程中碰到一类问题比如数据资源加载缓存机制的实现,用到了魔法方法中构造方法,其中__init__()和__new__是对象的构造器,合理运用将有效提高程序性能。

希望大家多结合自己的业务需求深刻理解,灵活运用,使得代码变得更加优雅。

一、__new__方法简介

接下来通过实例逐步详细阐述__ new __ 方法在类初始化过程中是什么样的存在!

1、初始化数据加载+解析类实例

class Solution(object):
    def __init__(self, name=None,data=None):
        self.name = name
        self.data = data
        #初始化加载数据
        self.xml_load(self.data)

    def xml_load(self,data):
        print("初始化init",data)

    def Parser(self):
        print("解析完成finish",self.name)

a = Solution(name="A111",data=10)
a.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))

初始化init 10
解析完成finish A111
初始化init 20
解析完成finish A112
<__main__.Solution object at 0x0000024A3AF28D48>
<__main__.Solution object at 0x0000024A3B055C48>
2517839809864
2517841042504

注:

1、代码实例化类过程

一般使用__init__()方法初始化一个类的实例,当代码中实例化一个类的时候,第一个调用执行的是__new__()方法,当定义的类中没有重新定义__new__()方法时候,Python会默认调用该父类的__new__()方法来构造该实例,new方法就是先创建一个空间,然后每次创建一个实例化的对象,然后用开辟的空间存放这个实例化对象; 再次创建一个实例化的对象的时候,再用new方法开辟一个空间存放实例化对象。注意只有继承了object的类才有此方法。

2、内存地址和对象可相互转换

#通过_ctypes的api进行对内存地址的对象
import _ctypes
obj = _ctypes.PyObj_FromPtr(id(a))
#打印出来通过内存地址寻找到的对象
print(obj)

print(id(a))与 print(id(b))打印出来的都是内存地址(10进制),print(a)与 print(b)返回了类的名称和对象的地址,但是两者并不相同。每次实例化类都会创建分配不同的对象地址,因此,代码实例化类过程中返回类对象的地址引用也就不同。

2、初始化数据加载重写new方法+解析类实例

class Solution:
    """
    注:new方法是为实例化对象创建空间的方法,现在new方法被改写,没有将实例化对象引用返回给python的解释器
    无法为实例化对象创建空间存储,所以运行代码会报错。也没有完成初始化操作。
    """

    def __new__(cls, *args, **kwargs):
        print("对象创建空间")
        cls.instance = super().__new__(cls)
        print(cls.instance)
        # return cls.instance   #若未返回实例对象引用,实例化方法将报错:AttributeError: 'NoneType' object has no attribute 'Parser'

    def __init__(self,name,data):
        self.name = name
        self.data = data
        self.xml_load(self.data)

    def xml_load(self,data):
        print("初始化init", data)

    def Parser(self):
        print("解析完成finish",self.data)

a = Solution("A111",10)
a.Parser()
print(id(a))

注:

1、__init__()方法和__new__()方法区别

__new__()方法用于创建实例,类实例化之前会首先调用,它是class的方法,是个静态方法。而__init__()方法用户初始化实例,该方法用在实例对象创建后被调用,它是实例对象的方法,用于设置类实例对象的一些初始值。

如果类中同时出现了__init__()方法和__new__()方法,则先调用__new__()方法后调用__init__()方法。__new__()方法是创建实例的第一步,执行完了需要返回创建的类的实例,否则则报错,无法执行__init__()方法。其中,__init__()方法将不返回任何信息。

2、重写__new__()方法

def __new__(cls, *args, **kwargs):
    print(cls)  # cls 代表的是Solution这个类本身<class'__ main __.Solution'>
    cls.instance = super().__new__(cls)  # object().__ new __()
    print(cls.instance)
    return cls.instance

super()与object.__new__(cls)都是在调用父类的new方法,必须把父类的new方法返回给函数,才能开辟空间,因此必须添加return。代码的执行顺序是:先执行new方法,然后执行init方法,最后是其它方法。

二、单例模式

单例模式最初的定义出现于《设计模式》:“保证一个类仅有一个实例,并提供一个访问它的全局访问点。”

单例的使用主要是在需要保证全局只有一个实例可以被访问的情况,比如系统日志的输出、操作系统的任务管理器等。

1、用new方法如何实现单例模式

class Solution:
    # 1、记录第一个被创建对象的引用,代表着类的私有属性
    _instance = None # 静态变量 存储在类的命名空间里的

    def __init__(self,name,data):
        self.name = name
        self.data = data
        self.xml_load(self.data)

    def __new__(cls, *args, **kwargs):
        # 2.判断该类的属性是否为空;对第一个对象没有被创建,我们应该调用父类的方法,为第一个对象分配空间
        if cls._instance == None:  
            # 3.把类属性中保存的对象引用返回给python的解释器
            cls._instance = object.__new__(cls)  # 3
            return cls._instance
        # 如果cls._instance不为None,直接返回已经实例化了的实例对象
        else:
            return cls._instance  # 必须把地址返回给new方法,让它有存储空间

    def xml_load(self,data):
        print("初始化init",self.name,data)

    def Parser(self):
        print("解析完成finish",self.name)

a = Solution("A11",10)  #第一次开辟一个对象空间地址,后面创建都是在该地址上进行的
a.Parser()
b = Solution("A12",20)  #b把a覆盖掉
b.Parser()
print(id(a))
print(id(b))
# 内存地址,而且它们的内存地址都是一样的
print(a.name)
print(b.name)

输出

初始化init A11 10
解析完成finish A11
初始化init A12 10
解析完成finish A12
2465140199816
2465140199816
A12
A12 

注:

1、单例模式始终只有一个空间,该空间一直重复利用。

首先定义一个类的私有属性_instance,用来记录第一个被创建对象的引用,如果cls._instance为None说明该类还没有实例化过,则实例化该类并返回实例对象。

通过以下数据测试可知,print(obj.name, obj.data)最后打印出来的都是A12,第一次打印"A11"时,属性为空,执行if语句开辟了一个空间存放该属性;从 第二次打已经开辟了空间 ,执行else语句,直接返回"A12"到原来的空间中,把前面的盖数据覆盖掉。

def task(id,data):
    obj = Solution("{0}".format(id), "{0}".format(data))
    print(obj.name, obj.data)

import threading
ID=["A11","A12","A13","A14","A12"]
DATA=[10,20,30,40,20]
for i in range(5):
    t = threading.Thread(target=task(ID[i],DATA[i]), args=[i, ])
    t.start()

输出

<__main__.Solution object at 0x00000221B2129148>
初始化init A11 10
A11 10
初始化init A12 20
A12 20
初始化init A13 30
A13 30
初始化init A14 40
A14 40
初始化init A12 20
A12 20

2、单例模式另外一种实现方法 

def __new__(cls,*args,**kwargs):
    # hasattr查询目标并判断有没有,not  1==1  返回的是False
    # if语句后面的
    # not 条件整体为True时,执行cls.instance = object....代码

    # if语句后面的
    # not 条件整体为False时,执行return代码
    if not hasattr(cls,"instance"):     # hasattr查、判断的作用
        cls.instance = object.__new__(cls)
    return cls.instance

2、如何控制类仅执行一次初始化方法

以上实现了单例模式对象空间的重复利用,但是有时候我们想初始化过程只加载一次,避免频繁请求浪费系统资源(如数据库连接请求数据)。

class Solution:
    #定义类变量
    # 记录第一个被创建对象的引用,代表着类的私有属性
    _instance = None
    #记录是否执行过初始化动作
    init_flag = False

    def __init__(self,name,data):
        self.name = name
        self.data = data
        #使用类名调用类变量,不能直接访问。
        if Solution.init_flag:
            return
        self.xml_load(self.data)
        # 修改类属性的标记
        Solution.init_flag = True

    def __new__(cls, *args, **kwargs):
        # 判断该类的属性是否为空;对第一个对象没有被创建,我们应该调用父类的方法,为第一个对象分配空间
        if cls._instance == None: 
            # 把类属性中保存的对象引用返回给python的解释器
            cls._instance = object.__new__(cls)  
            return cls._instance
        #如果cls._instance不为None,直接返回已经实例化了的实例对象
        else:
            return cls._instance 

    def xml_load(self,data):
        print("初始化init",self.name,data)

    def Parser(self):
        print("解析完成finish",self.name)

a = Solution("A11",10)  #第一次实例化对象地址,后面创建都是在该地址上进行的
a.Parser()
b = Solution("A12",20)  #b把a覆盖掉
b.Parser()
print(id(a))
print(id(b))
print(a.name)
print(b.name)

输出

初始化init A11 10
解析完成finish A11
解析完成finish A12
2280855720328
2280855720328
A12
A12 

注:

1、单例模式下仅加载一次初始化过程。

这时候我们在类空间中再添加一个init_flag属性来记录是否已经执行过初始化操作即可实现加载一次初始化过程。从以上两次实例化过程结果来看,对象引用地址不变,结果被最后一次实例化数据覆盖且初始化init只被打印一次。

2、单例模式下一次资源加载注意点

单例模式下控制类仅进行一次初始化过程适用于资源一次性加载进缓存的过程,对于多进程应用可采用多例模式实现。

三、多例模式

多个实例对象空间引用地址完全独立,从而保持避免不同请求资源不被占用。将同一个对象请求归为同一个实例。

class Solution:
    ##定义类实例化对象字典,即不同的实例对象对应不同的对象空间地址引用
    _loaded = {}

    def __init__(self,name,data):
        self.name = name
        self.data = data
        self.xml_load(self.data)

    def __new__(cls, name,*args):
        if cls._loaded.get(name) is not None:
            client = cls._loaded.get(name)
            print(f"已经存在访问对象 {name}")
            print(client)
            return client
        # 把类属性中保存的对象引用返回给python的解释器
        print(f"正在创建访问对象 {name}")
        client = super().__new__(cls)
        # 为该类实例name添加一个空间对象地址引用
        print(client)    
        cls._loaded[name] = client
        return client

    def xml_load(self,data):
        print("初始化init",self.name,data)

    def Parser(self):
        print("解析完成finish",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"{a is b}")
    print(a.name)
    print(b.name)
    print(c.name)

注:

1、多例模式始终具有多个空间,不同空间完全独立。

我们在类空间中定义类实例化对象字典,即建立不同的实例对象和对象空间地址引用键值对,从而实现多例模式。通过类字典判断实例对象是否创建,节省创建的成本。

2、多例模式测试过程

当创建相同的实例对象name="A11"时,程序首先在实例池中搜索cls._loaded.get(name),若存在则直接返回已创建的实例对象空间。多例模式完美的实现了不同访问对象具体不同的实例化对象地址。

3、多例模式下缓冲机制的实现

进一步优化多例模式初始化过程,比如读取文件或者数据库时仅进行一次初始化加载。

class Solution:
    ##定义类实例化对象字典,即不同的实例对象对应不同的对象空间地址引用
    _loaded = {}

    def __new__(cls, name,data,*args):
        if cls._loaded.get(name) is not None:
            client = cls._loaded.get(name)
            print(f"已经存在访问对象 {name}")
            print(client)
            return client
        print(f"正在创建访问对象 {name}")
        # 把类属性中保存的对象引用返回给python的解释器
        client = super().__new__(cls)
        print(client)
        # 为该类实例name添加一个空间对象地址引用
        cls._loaded[name] = client
        client._init_db(name,data)
        return client

    def _init_db(self,name,data):
        self.name = name
        self.data = data
        self.xml_load(self.data)

    def xml_load(self,data):
        print("初始化init",self.name,data)

    def Parser(self):
        print("解析完成finish",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"{a is b}")
    print(a.name)
    print(b.name)
    print(c.name)

输出

正在创建访问对象 A11
<__main__.Solution object at 0x0000024198989148>
初始化init A11 10
解析完成finish A11
已经存在访问对象 A11
<__main__.Solution object at 0x0000024198989148>
解析完成finish A11
正在创建访问对象 A12
<__main__.Solution object at 0x00000241989891C8>
初始化init A12 20
解析完成finish A12
True
A11
A11
A12

注:多例模式下多个实例化对象均只进行一次初始化过程。

重写__new__方法中每个实例对象创建后绑定初始化_init_db()方法执行一次,后面遇到同一个实例对象将不会发生什么,直接返回已创建的实例对象。从测试结果来看,创建相同的实例对象name="A11"时,第二次将略过初始化数据加载过程,很好的实现了缓存机制。

总结

本文结合项目背景详细介绍了__new__方法实现单例模式和多例模式以及缓存机制的实现!

1、__new__ 方法是在类创建实例的时候自动调用的。

2、 实例是通过类里面的 __ new __ 方法是在类创建出来的。

3、 先调用__new__ 方法创建实例,再调用 __ init __方法初始化实例。

4、 __new__ 方法,后面的括号里面的cls代表的是类本身。

5、__new__ 方法,判断类属性为空就去开辟空间,否则复用原来的地址。

更多的特殊方法比如1、自我描述方法:__repr__2、析构方法:__del__ 3、列出对象所有属性(包括方法)名:__dir__4、__dict__属性:查看对象内部所有属性名和属性值组成的字典5、__getattr__\__setattr__等。

当然还有metaclass类的__new__方法,可以动态修改程序中的一批类,这个功能在开发一些基础性的框架时非常有用,可以使用metaclass为某一批需要通用功能的类添加方法。

以上就是详解Python中__new__方法的作用的详细内容,更多关于Python __new__的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
编写同时兼容Python2.x与Python3.x版本的代码的几个示例
Mar 30 Python
介绍Python的Django框架中的QuerySets
Apr 20 Python
以视频爬取实例讲解Python爬虫神器Beautiful Soup用法
Jan 20 Python
python3.4用循环往mysql5.7中写数据并输出的实现方法
Jun 20 Python
Django  ORM 练习题及答案
Jul 19 Python
使用opencv将视频帧转成图片输出
Dec 10 Python
pytorch::Dataloader中的迭代器和生成器应用详解
Jan 03 Python
用python介绍4种常用的单链表翻转的方法小结
Feb 24 Python
Python调用C语言程序方法解析
Jul 07 Python
python中sys模块是做什么用的
Aug 16 Python
Pandas的数据过滤实现
Jan 15 Python
关于Python中进度条的六个实用技巧分享
Apr 05 Python
利用Python将list列表写入文件并读取的方法汇总
Mar 25 #Python
利用Python多线程实现图片下载器
Python实现灰色关联分析与结果可视化的详细代码
聊聊基于pytorch实现Resnet对本地数据集的训练问题
pycharm安装深度学习pytorch的d2l包失败问题解决
利用For循环遍历Python字典的三种方法实例
Mar 25 #Python
Python装饰器详细介绍
Mar 25 #Python
You might like
解决yii2左侧菜单子级无法高亮问题的方法
2016/05/08 PHP
php出租房数据管理及搜索页面
2017/05/23 PHP
jquery加载页面的方法(页面加载完成就执行)
2011/06/21 Javascript
JavaScript中使用arguments获得函数传参个数实例
2014/08/27 Javascript
jQuery中$.get、$.post、$.getJSON和$.ajax的用法详解
2014/11/19 Javascript
js插件YprogressBar实现漂亮的进度条效果
2015/04/20 Javascript
JavaScript统计网站访问次数的实现代码
2015/11/18 Javascript
js H5 canvas投篮小游戏
2016/08/18 Javascript
浅谈React 属性和状态的一些总结
2016/11/21 Javascript
原生JS实现图片轮播效果
2016/12/26 Javascript
让bootstrap的carousel支持滑动滚屏的实现代码
2017/11/27 Javascript
Vue.js实现分页查询功能
2020/11/15 Javascript
Vue侧滑菜单组件——DrawerLayout
2017/12/18 Javascript
JS文件中加载jquery.js的实例代码
2018/05/05 jQuery
JavaScript实现学生在线做题计时器功能
2018/12/05 Javascript
浅谈Vue.js组件(二)
2019/04/09 Javascript
微信小程序如何调用图片接口API并居中显示
2019/06/29 Javascript
基于ssm框架实现layui分页效果
2019/07/27 Javascript
浅谈vue项目用到的mock数据接口的两种方式
2019/10/09 Javascript
对vuex中getters计算过滤操作详解
2019/11/06 Javascript
Vue插件之滑动验证码用法详解
2020/04/05 Javascript
JS实现简易日历效果
2021/01/25 Javascript
使用python搭建Django应用程序步骤及版本冲突问题解决
2013/11/19 Python
python获取外网IP并发邮件的实现方法
2017/10/01 Python
Python延时操作实现方法示例
2018/08/14 Python
python制作简单五子棋游戏
2019/06/18 Python
django admin.py 外键,反向查询的实例
2019/07/26 Python
pandas数据处理之绘图的实现
2020/06/15 Python
Python Merge函数原理及用法解析
2020/09/16 Python
Python3+PyCharm+Django+Django REST framework配置与简单开发教程
2021/02/16 Python
HomeAway的巴西品牌:Alugue Temporada
2018/04/10 全球购物
澳大利亚领先的优质葡萄酒拍卖会:Langton’s Fine Wines
2019/03/24 全球购物
求两个数的乘积和商数,该作用由宏定义来实现
2013/03/13 面试题
酒店节能降耗方案
2014/05/08 职场文书
金融系应届毕业生求职信
2014/05/26 职场文书
mysql的Buffer Pool存储及原理
2022/04/02 MySQL