Python_单例

1.设计模式和单例设计模式概念

在我们开始学习软件开发之前,已经有无数的前辈们投入到这项事业, 前辈啊,在工作中就是来编写代码,解决一个又一个问题,然后呢,他们在针对自己曾经解决过的问题,进行不断的归纳和总结,先是对问题进行分类,然后再来确定一个成熟的解决方案,不同的问题提供不同的解决套路,这就叫做设计模式.

 

既然前辈们已经把解决问题的套路确定下来,那么我们只需要学习一下这个套路,在今后的工作中一旦遇到这一类问题,就可以使用对应的套路来解决问题.

这样在开发时,就可以不用再自己花费脑筋来思考这个问题应该怎么样解决, 这个就是学习设计模式的好处.

介绍一个单例设计模式,要想理解清楚单例设计模式的好处,先来看几个应用场景,那举了一个音乐播放的例子,举了一个回收站的例子,举了一个打印机的例子,这三个例子有一个共同特点,这三个对象都只有唯一的一个存在. 那单例设计模式,所谓单例啊,就是单个实例, 当定义完成一个类之后,使用类名加括号的方式可以创建一个对象,而单例设计模式解决的问题呢,就是无论执行多少次类名加括号, 返回的对象内存地址永远都是相同的,

 

如果返回的内存地址是相同的,说明利类名加括号创建出来的对象, 在内存中永远只有唯一的一个实例,这个就叫做单例设计模式.

一句话讲设计模式就是前人总结出来的套路,碰到不同的问题,使用不同的套路就可以了,而不需要我们再自己开动脑筋来思考这个问题应该怎么解决,这个就叫做设计模式。而单例设计模式要解决的问题就是, 使用类创建的对象在系统中永远只有唯一的一个实例,这个就是单例设计模式在开发中能够解决的问题.

2.new方法的作用

以两个下划线开头, 两个下划线结尾的方法是一个内置方法,而__new__方法呢是由object这个基类提供了一个内置的静态方法,这个方法的作用是 ,当使用一个类名创建对象的时候,Python解释器会帮做两件事情,第1件事情为对象在内存分配空间,第2件事情为对象进行初始化,初始化方法已经学习过了, 那为对象分配空间就是__new__方法.

 

单例设计模式的目的就是为对象在内存分配空间的时候,永远只会返回一个唯一的固定的内存空间,这样就能够保证在内存中这个类的对象只有唯一的一份,这个就叫做单例,而要想达到这个效果,就需要了解一下,为对象分配空间的__new__方法,明确了目的之后,接下来看一下,在__new__方法内部都做哪些事情,__new__方法主要做两件事情,第1件事情呢在内存中为对象分配空间, 分配了空间之后, 注意__new__方法是有返回值的, __new__方法呢,需要把对象的引用作为返回值, 返回给Python解释器。

当Python解释器拿到了对象的引用之后,就会把对象的引用传递到初始化方法的第1个参数,

  

初始化方法拿到了对象引用之后,就可以在方法的内部, 针对对象来定义实例属性了,这个就是__new__方法和初始化方法的分工. 一句话讲__new__方法负责给对象分配空间,而初始化方法负责给对象初始化.

一句话讲__new__方法是由object这个基类提供了一个内置的静态方法, __new__方法的作用,有两个,第一个为对象在内存分配空间,第二个把分配空间的内存地址直接返回给python的解释器, python的解释器拿到对象引用之后, 就可以把这个引用传递到初始化方法内部,

 

由初始化方法对传递进来的对象进行初始化,而之所以要学习__new__方法,就是因为需要对分配空间的方法进行改造,改造的目的呢,就是当使用类名创建对象的时候,无论执行多少次,在内存中永远只会创建出一个对象的实例,这样就可以达到单例设计模式的目的了.

3. 重写new方法

对__new__方法方法的重写做一个演练, 先来定义 MusicPlayer类, 然后再MusicPlayer类的内部重写一下由object 基类提供的__new__方法, 通过对这个方法的重写, 来强化一下__new__方法要做的事情, 第一件事情, 再内存中为对象分配空间, 第二件事情, 返回对象的引用, 同时还需要验证一下,当使用类名创建对象的时候,Python解释器会自动调用__new__方法.

 

针对__new__方法的重写,做一个演练,先来定一个先来定义 MusicPlayer 音乐播放器的类,然后呢,在音乐播放器先来定义 MusicPlayer类的内部, 重写一下由object基类提供的__new__方法. 通过对这个方法的重写,共同来强化一下__new__方法要做的两件事情,第一件事情在内存中为对象分配空间,第二件事情返回对象的引用.

同时还需要验证一下,当使用类名创建对象的时候,Python的解释器会自动帮我们调用__new__方法. 目的明确之后,首先使用class关键字来定义一个MusicPlayer 的类,让这个类啊,继承自object基类。类名准备完成,先使用def关键字,找到初始化方法,先在初始化方法内部做一个输出, 播放器初始化,只需要做一个简单的输出就可以.

 

现在一个简单的音乐播放器类已经准备完成,就可以在主程序中来创建一个播放器对象.

写一下创建播放器对象,要创建对象,就先给对象起个名字,叫做player,然后使用MusicPlayer创建一个对象.

现在就在主程序中使用print函数, 把player这个对象做一个输出,

 

运行一下程序,控制台输出了播放器初始化,同时把对象的内存地址也做了一个输出。

 

接下来,就把重点啊放在这一小节演练的重点上,这一小节要重写object基类提供的__new__方法,那要重写方法,就使用def关键字,然后敲两个下划线,找到__new__方法,__new__方法一共可以接收三个参数,第一个参数cls是哪一个类调用就传递哪一个类,第二个参数,一个星星表示这个参数是一个多值的元组参数,而第三个参数,两个星星呢,两个星星就表示这个参数是一个多值的字典参数,

 

把重点放在__new__方法的重写上,先来验证第一个目标,要验证使用类名创建对象的时候,__new__方法会被自动调用,写一下注释, 创建对象时,new方法会被自动调用.

怎么样能够验证这个方法会被自动掉呀? 在这里使用print函数做个输出就可以.就写一下注释, 创建对象分配空间,因为new方法最主要的作用就是分配空间,

现在重写了父类的new方法,并且只是写了一个print语句,写了print语句, 这意味着父类方法不能够被执行了. 现在运行一下程序,控制台输出了 创建对象分配空间,

 

这个输出意味着new方法已经被调用了,但是控制台输出了一个None,这个None就是在第16行打印player对象时,打印输出了一个空对象,

而且初始化方法并没有被调用,这是什么原因呢?当再重写new方法的时候,一定注意要返回分配的内存空间,

 

如果不返回, python的解释器就没有办法得到分配了空间的对象引用,那如果得不到对象引用, python的解释器呢,就不会来调用初始化方法,把对象的引用传递到初始化方法内部,

 

刚刚重写了new方法之后,没有做任何的返回,

  

没有返回, python的解释器就得不到这个对象的内存地址,因此呢,初始化方法不会被调用,同时程序执行时, 在控制台会打印输出一个空对象,那现在把光标放在new方法内部,new方法重要的两件事情是, 第一件事情就是为对象分配空间,第二件事情呢是返回对象的引用.

为对象分配空间, 直接调用父类的方法就可以了, 因为object 这个基类中, 默认的__new__已经可以实现为对象分配空间的动作, 所以在这里可以直接调用父类的方法.

要调用父类方法, 应该找一个特殊的对象, super, 找到__new__方法, __new__方法是一个静态方法, 所以在调用__new__方法的时候, 必须要把第一个参数cls 传递给这个方法, 

 现在调用了父类的方法之后, 要把方法的调用结果返回, 可以直接在第9行,加一个return, 或者定义一个变量接收一下父类的返回结果, 

一个变量接收了父类方法的返回结果之后, 就可以把这个变量直接返回, 现在使用 return 关键字来返回 instance 这个变量, 这样方法就改造完成了.

 

 之前在__new__方法中, 直接单纯的调用print 函数, 在程序运行时, 控制台只会输出一个None 的空对象, 而现在在__new__方法中增加了两句代码, 第1句代码,调用了父类的__new__方法, 

 第2句代码把记录的结果返回给Python 的解释器, 

现在再运行一下程序, 控制台输出了"创建对象, 分配空间", 分配完空间之后, "播放器初始化", 初始化完成之后, 主程序中打印了player的内存地址,3b00.

 

 

程序又能够正常执行了.

现在回顾一下new方法的重写过程,第一步,使用print函数覆盖了父类的方法实现,一旦覆盖之后,整个程序就没有办法正常执行了,因为new方法必须要返回对象的引用, 不返回对象的引用, python的解释器就不会调用初始化方法, 而主程序中打印输出, 也只会输出一个空对象None,那么,要编写一个完整的new方法,就先调用一下父类的new方法实现,并且使用一个变量记录一下返回结果,

 

同时注意啊,New方法是一个静态方法,所以在调用父类方法实现的时候,需要把cls 这个参数传递给父类的new方法,这样呢,才能够得到一个给对象分配了空间之后的引用,当拿到了对象引用之后,再使用return关键字,把对象的引用返回,

 

这样python的解释器就能够自动的调用初始化方法,初始化方法完成之后, 就可以在主程序中把创建好的播放器对象打印在控制台了,

 

一句话讲,__new__方法,重写的代码非常的固定,只需要记住,New方法中一定要返回父类方法调用new方法的结果就可以,而且new方法是一个静态方法,所以在调用父类方法时,必须要主动传递cls这个参数.

class MusucPlayer(object):

    def __new__(cls, *args, **kwargs):

        # 1. 创建对象时, new方法会被自动调用
        print("创建对象,分配空间")

        # 2. 为对象分配空间
        instance = super().__new__(cls)

        # 3. 返回对象的引用
        return instance 

    def __init__(self):
        print("播放器初始化")

# 创建播放器对象
player = MusucPlayer()

print(player)


 

 

4.单例设计模式分析

使用单例设计模式来设计一个单例类, 所谓单例类啊,就是使用这个类在创建对象的时候,无论调用多少次创建对象的方法,得到的结果永远是内存中唯一的那个对象,

 

这个就叫做单例, 那怎么样验证使用这个类创建出来的对象是同一个对象呢?只需要多调几次创建对象的方法,然后输出一下方法的返回结果, 如果内存地址是相同的,说明多次调用方法得到的结果本质上还是同一个对象. 

 

使用class关键字来定义一个MusicPlayer类, 让MusicPlayer类继承自object这个基类,然后呢使用pass这个关键字先做一个占位,

 

现在先要做一个准备工作,就是在主程序中啊,创建多个对象来对比一下多个对象的内存地址是否相同,那现在先创建第1个对象,然后使用print函数把player做一个输出,

 

紧接着再创建第2个对象,

 

写完先来运行一下程序,

 

看看这两个对象的内存地址是否相同, 控制台输出第1个对象的内存地址,0978,第2个对象的内存地址0a20,

 

现在这两个对象的内存地址并不一样,内存地址不一样就说明player1和player2是两个完全不同的对象. 而单例设计模式的目的就是无论调用多少次创建对象的方法,得到的对象引用应该是相同的,也就是控制台输出的内存地址应该一模一样,那怎么样做到这一点呢?要想实现单例设计模式啊,首先需要定义一个类属性,我们定义一个instance的类属性,让这个类属性啊来记录一下单例对象的引用.

当一个类定义完成运行程序的时候,内存中并没有这个类创建的对象,需要调用一下创建对象的方法,内存中才会有第1个对象,那因此啊,就把这个类属性它的初始值设置为None,None就表示空对象,因为程序在执行的时候,内存中还没有第1个对象,那当类属性定义完成, 调用创建对象的方法的时候,当调用父类方法为这个对象分配空间之后,

就使用这个类属性啊,来记录一下父类方法的返回结果,

 

父类方法的返回结果就是第1个对象在内存中的地址,使用这个类属性记录一下,

 

当再一次调用创建对象的方法时,再次创建时, 第1个对象在内存中已经存在了,那么就直接把第1个对象的引用做一个返回,而不再调用父类的分配空间这个方法,

 

不再调用分配空间这个方法, 就不会在内存中为这个类的其他对象分配额外的空间,而只是把之前记录的第1个对象的引用做一个返回,

 

再次调用的时候只是把第1个对象的引用做一个返回.

这样呢就能够做到无论调用多少次创建对象的方法,得到的永远是第1个创建出来的对象引用,这个就是使用单例设计模式解决 在内存中只创建 唯一一个实例 的解决办法.

一句话讲定义一个类属性, 类属性的初始值为None, 当第1次调用创建对象方法时,就在类属性中记录第1个对象的引用,

 

当再次调用创建对象方法时,就直接返回第1个对象的引用就可以.

class MusicPlayer(object):

    pass

# 创建多个对象
player1 = MusicPlayer()
print(player1)

player2 = MusicPlayer()
print(player2)

 

 

5.单例设计模式代码实现

接下来就结合这张流程图来共同完成一下单例设计模式,

 

要想实现单例,第1步应该在类中定义一个类属性,由这个类属性保存住第1个被创建出来的对象引用,先增加一个注释, 第1步啊,要定义一个类属性,由这个类属性记录住被第1个创建对象的引用,

 

注释写完, 就给类属性起个名字, instance,然后给他设置一个初始值,None, 因为类定义完成,第1个对象还没有被创建,必须要主动调用下创建对象的方法,第1个对象才能够被创建,因此这个类属性的初始值,先把它定义为None,

 

类属性准备完成,接下来要改造__new__方法,因为__new__方法是负责分配空间的, 那要改造__new__方法,就使用def 关键字来找到父类中已经提供好的__new__方法,

 

方法找到之后,现在方法中增加几个注释, 来明确一下在方法内部要做的事情,第1步应该判断类属性,看看类属性是不是一个空对象,如果是空对象, 说明第1个对象还没有被创建, 那接下来再来编写第2个注释, 如果对象没有被创建,应该调用父类的方法为第1个对象分配空间,只有第1个对象分配空间之后, 才能使用类属性进行记录. 当使用类属性记录第1个对象分配的空间之后,第3步只需要把类属性instance 中保存的引用, 返回给Python解释器就可以, 3个步骤写完.

 

那现在就把光标放在第1个注释下方,要想进行判断, 应该使用if 来判断,要想判断类属性,就可以使用cls这个参数,加上一个点,再点后面找到类属性instance,要判断类属性是否是空对象, 应该使用is这个身份运算符,在is后面跟上一个None,如果类属性是一个None, 就可以调用父类的方法为第1个对象分配空间. 把这个注释向上挪一挪,把光标放在if的下方, 来调用一下父类方法为第1个对象分配空间.要调用父类方法应该使用super 这个特殊的对象,在后面跟上一个点,然后找到要调用的父类方法__new__方法,同时注意__new__方法是一个静态方法,必须要把cls传递给这个方法, 现在就把cls传递给父类的__new__方法,

 

父类的方法调用完成,需要使用类属性来记录一下父类方法执行的结果,就把光标放在第11行前面,然后使用cls这个参数, 点来找到instance这个类属性,然后呢,通过赋值语句来记录一下父类方法执行的返回结果.

 

当这句代码执行完成, 代码执行到第14行时,类属性中一定会保存住对象的引用.

既然保存了对象引用,就使用return关键字,把class点instance做个返回,

 

代码改造完成, 123,写了三句代码,同时定义了一个类属性,现在运行一下程序,

 

验证一下单例设计模式的运行结果,

 

控制台输出了两个对象的地址, 都是db00, 地址完全一模一样,地址一样就说明player1和player2本质上是相同的一个对象,这个就是使用单例设计模式对__new__方法进行改造,在__new__方法内部, 来判断一下类属性是否被设置初始值, 如果没设置,就调用父类方法为第1个对象分配空间,

 

如果类属性已经有了值,就直接把类属性中保存的第1个对象引用直接返回,这样呢,在外界无论调用多少次创建对象的方法,得到的对象内存地址永远都是相同的,这个就是使用单例设计模式来开发设计了一个单例的类.

class MusicPlayer(object):

    # 记录第一个被创建对象的引用
    instance = None

    def __new__(cls, *args, **kwargs):

        # 1. 判断类属性是否是空对象
        if cls.instance is None:
            # 2. 调用父类的方法, 为第一个对象分配空间
            cls.instance = super().__new__(cls)

        # 3. 返回类属性保存的对象引用
        return cls.instance
        
# 创建多个对象
player1 = MusicPlayer()
print(player1)

player2 = MusicPlayer()
print(player2)


 

 

6.初始化动作只执行一次

再针对单例设计模式做一个扩展,对new方法进行了改造, 改造之后,得到的结果,就是每一次通过类名创建对象的时候,得到的都是第1次创建出来的对象引用,现在已经能够实现,内存中只有唯一的一个对象. 但是啊,当使用类名创建对象的时候,Python解释器都会自动调用两个方法,

 

第1个方法__new__方法, 为对象分配空间,这个方法已经改造过了. 那现在看第2个方法,当分配完空间之后, Python解释器还会自动调用初始化方法, 在初始化方法中为对象进行初始化.

在上一小节完成的代码中,使用了几次类名创建对象初始化方法就会被调用几次.

先把__new__方法折叠一下,

 

把注意力放在初始化方法中,在初始化方法内部使用print函数做个输出,初始化播放器,

 

运行一下程序,看看初始化方法到底会被执行几次, 控制台输出了初始化播放器,初始化播放器,

 

在代码中创建了两次对象,因此初始化方法就会被调用两次,

 

在开发中有可能会碰到一种需求,就是希望让初始化的动作只被执行一次,Python解释器会自动调用初始化方法,这个动作并不能够被限制. 但是在开发中需要让初始化的动作只被执行一次,什么是初始化的动作? 所谓初始化的动作就是写在初始化方法内部, 对对象进行初始化的代码. 那么如果希望这个初始化动作只被执行一次,应该怎么做呢?分享一个思路,

 

可以再定义一个类属性,给这个类属性起个名字, 叫做初始化标记,这个标记的初始值,把它设置为False,也就表示还没有执行过初始化动作,初始值为False.

那么在初始化方法被执行的时候,先来判断一下初始化动作的标记,如果围假就表示还没有执行过初始化动作,那么就执行一下初始化的动作,当初始化动作执行完成之后,要把这个标记设计为True. 当这个标记设置为True之后,下一次在调用初始化方法的时候, 就不会执行初始化动作了, 这个就是来解决让初始化动作只被执行一次的思路,初始化方法的调用不能限制,但是可以用特殊的方式来解决一下,让初始化的动作只被执行一次.

接下来就把代码完成一下,按照刚刚的思路,应该再定义一个类属性,让这个类属性啊, 记录是否执行过初始化方法,那现在就来定义一个类属性,首先给类属性起个名字,然后给它设置一个初始值False,

 

也就表示最开始还没有执行过初始化的动作,那现在把这个方法两个字改成动作,这样呢,会更加准确一些,

 

一个类属性定义完成,现在就把光标放在初始化方法内部,先通过注释的方式来确认一下初始化方法内部应该怎样处理,既然定义了一个类属性,第1步应该来判断一下类属性,看看是否执行过初始化动作,写下判断是否执行过初始化动作,然后呢,

 

如果执行过直接返回就可以, 那如果没有执行, 再来执行初始化动作, 写一下注释,如果没有执行过,再执行初始化动作,

 

执行完初始化动作之后, 不要忘记要把类属性的值做一个修改,再增加一个注释, 修改类属性的标记, 三个步骤确定完成,

 

现在就来逐一填写一下代码,第1步要判断类属性, 可以使用if关键字,同时注意, 要判断的是类属性,就应该使用类名点的方式来找到那个属性,

 

如果类属性为真,就不需要执行下方的代码, 不需要执行下方的代码,可以直接用return 做一个返回,那接下来第2步就是来执行初始化的动作,就仍然使用print来输出一下初始化播放器,

 

现在初始化动作执行完成,应该修改一下类属性这个标记, 那要修改类属性,仍然使用类名点的方式,找到这个初始化标记,同时把它设置为True, 设置为True就表示已经执行过了初始化动作,现在在初始化方法内部又增加了三句代码,

 

现在再来运行一下程序,看看创建了两个对象,初始化动作会被执行几次来,

 

控制台只输了一次初始化播放器, 因为初始化动作执行完成, 类属性的标记就被设置为真,

 

下一次再调用初始化方法的时候,类属性标记为真就直接返回,而不会执行下方的初始化动作,这个就是使用单例设计模式时,怎么样让初始化的动作只执行一次.

一句话讲需要再定一个属性,

 

让这个类属性标记一下是否执行过初始化工作,然后在初始化方法内部, 先来判断一下这个标记,如果为真直接返回,如果为假, 再执行初始化动作.

class MusicPlayer(object):

    # 记录第一个被创建对象的引用
    instance = None

    # 记录是否执行过初始化动作
    init_flag = False

    def __new__(cls, *args, **kwargs):

        # 1. 判断类属性是否是空对象
        if cls.instance is None:
            # 2. 调用父类的方法, 为第一个对象分配空间
            cls.instance = super().__new__(cls)

        # 3. 返回类属性保存的对象引用
        return cls.instance

    def __init__(self):

        # 1. 判断是否执行过初始化动作
        if MusicPlayer.init_flag:
            return

        # 2. 如果没有执行过, 再执行初始化动作
        print("初始化播放器")

        # 3. 修改类属性的标记
        MusicPlayer.init_flag = True
        


# 创建多个对象
player1 = MusicPlayer()
print(player1)

player2 = MusicPlayer()
print(player2)


 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值