十一、python实现单例模式


单例模式以及Python实现

Python单例模式(Singleton)的N种实现

1、单例模式介绍

单例模式就是确保一个类只有一个实例.当你希望整个系统中,某个类只有一个实例时,单例模式就派上了用场.
比如,某个服务器的配置信息存在在一个文件中,客户端通过AppConfig类来读取配置文件的信息.如果程序的运行的过程中,很多地方都会用到配置文件信息,则就需要创建很多的AppConfig实例,这样就导致内存中有很多AppConfig对象的实例,造成资源的浪费.其实这个时候AppConfig我们希望它只有一份,就可以使用单例模式.

2、实现单例模式的几种方法

2.1 使用模块

python的模块就是天然的单例模式,因为模块在第一次导入的时候,会生成.pyc文件,当第二次导入的时候,就会直接加载.pyc文件,而不是再次执行模块代码.如果我们把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了.

  • 代码
class Singleton(object):
    def foo(self):
        pass
singleton = Singleton()
  • 使用
from singleton.mysingleton import singleton

2.2 使用装饰器

装饰器里面的外层变量定义一个字典,里面存放这个类的实例.当第一次创建的收,就将这个实例保存到这个字典中.
然后以后每次创建对象的时候,都去这个字典中判断一下,如果已经被实例化,就直接取这个实例对象.如果不存在就保存到字典中.

# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/6 10:22'
 
 
def singleton(cls):
    # 单下划线的作用是这个变量只能在当前模块里访问,仅仅是一种提示作用
    # 创建一个字典用来保存类的实例对象
    _instance = {}
 
    def _singleton(*args, **kwargs):
        # 先判断这个类有没有对象
        if cls not in _instance:
            _instance[cls] = cls(*args, **kwargs)  # 创建一个对象,并保存到字典当中
        # 将实例对象返回
        return _instance[cls]
 
    return _singleton
 
 
@singleton
class A(object):
    a = 1
 
    def __init__(self, x=0):
        self.x = x
        print('这是A的类的初始化方法')
 
 
a1 = A(2)
a2 = A(3)
print(id(a1), id(a2))

2.3 使用类

思路就是,调用类的instance方法,这样有一个弊端就是在使用类创建的时候,并不是单例了.也就是说在创建类的时候一定要用类里面规定的方法创建
会有问题

# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/6 11:06'
 
 
class Singleton(object):
    def __init__(self,*args,**kwargs):
        pass
 
    @classmethod
    def get_instance(cls, *args, **kwargs):
        # 利用反射,看看这个类有没有_instance属性
        if not hasattr(Singleton, '_instance'):
            Singleton._instance = Singleton(*args, **kwargs)
 
        return Singleton._instance
 
 
s1 = Singleton()  # 使用这种方式创建实例的时候,并不能保证单例
s2 = Singleton.get_instance()  # 只有使用这种方式创建的时候才可以实现单例
s3 = Singleton()
s4 = Singleton.get_instance()
 
print(id(s1), id(s2), id(s3), id(s4))

2.4 基于__new__方法实现的单例模式(推荐使用,方便)

1、一个对象的实例化过程是先执行类的__new__方法,如果我们没有写,默认会调用object的__new__方法,返回一个实例化对象,然后再调用__init__方法,对这个对象进行初始化,我们可以根据这个实现单例.
2、在一个类的__new__方法中先判断是不是存在实例,如果存在实例,就直接返回,如果不存在实例就创建.

# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/6 13:36'
import threading
 
 
class Singleton(object):
    _instance_lock = threading.Lock()
 
    def __init__(self, *args, **kwargs):
        pass
 
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            with Singleton._instance_lock:
                if not hasattr(cls, '_instance'):
                    Singleton._instance = super().__new__(cls)
 
            return Singleton._instance
 
 
obj1 = Singleton()
obj2 = Singleton()
print(obj1, obj2)
 
 
def task(arg):
    obj = Singleton()
    print(obj)
 
 
for i in range(10):
    t = threading.Thread(target=task, args=[i, ])
    t.start()

2.5 使用函数装饰器实现单例

def singleton(cls):
    _instance = {}

    def inner():
        if cls not in _instance:
            _instance[cls] = cls()
        return _instance[cls]
    return inner
    
@singleton
class Cls(object):
    def __init__(self):
        pass

cls1 = Cls()
cls2 = Cls()
print(id(cls1) == id(cls2))

3、实战

Python 中的单例

3.1 简单实现

class MusicPlayer(object):

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

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

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

        # 判断类属性是否是空对象
        if cls.instance is None:

            # 调用父类的方法,为第一个对象分配空间
            cls.instance = super().__new__(cls)
        # 返回类属性保存的对象引用
        return cls.instance

    def __init__(self):
        # 判断是否执行过初始化动作
        if MusicPlayer.init_flag:
            return
        # 如果没有执行过,在执行初始化动作
        print("初始化播放器")
        # 修改类属性的标记
        MusicPlayer.init_flag = True

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

play2 = MusicPlayer()
print(play2)

3.2 实际应用配置文件

import configparser
import json
import os
import requests
from base import baselog

region_file = 'apiserver.conf'
CONFIG_PATH = os.path.dirname(os.path.abspath(__file__))
file = os.path.join(CONFIG_PATH, region_file)
domain_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'domain.conf')
log = baselog.PyLogger().logger

class Config(object):

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

    # 记录是否执行过初始化动作
    init_flag = False
    config = configparser.ConfigParser()
    if os.path.isfile(domain_file):
        config.read(domain_file)
        rds_host = config.get('rds', 'rds_host')
        rds_endpoint = config.get('rds', 'rds_endpoint')

        vpc_host = config.get('vpc', 'vpc_host')
        vpc_endpoint = config.get('vpc', 'vpc_endpoint')

        ak = config.get('account', 'ak_normal')
        sk = config.get('account', 'sk_normal')
        log.info('rds_host: {0}'.format(rds_host))
        log.info('rds_endpoint: {0}'.format(rds_endpoint))
        log.info('vpc_host: {0}'.format(vpc_host))
        log.info('vpc_endpoint: {0}'.format(vpc_endpoint))
        log.info('ak: {0}'.format(ak))
        log.info('sk: {0}'.format(sk))
    else:
        config.read(file)
        ak = config.get('account', 'ak_normal')
        res = {}
        # 判断ic接口
        ic_flage = True
        try:
            ednpoint = config.get('ic', 'api_gateway_url')
            result = requests.get(ednpoint)
            res1 = json.loads(result.text)
            log.info('test endpoint ic: {0}'.format(res1))

            account = config.get('ic', 'iam_url')
            result = requests.get(account)
            res2 = json.loads(result.text)
            log.info('test account ic: {0}'.format(res2))
        except Exception as e:
            ic_flage = False
            log.warning('test get ic failed: {0}'.format(e))

        if ak == 'None' and ic_flage:
            try:
                ednpoint = config.get('ic', 'api_gateway_url')
                result = requests.get(ednpoint)
                res = json.loads(result.text)['data']['api_gateway'][0]
            except Exception as e:
                log.warning('get ic failed: {0}'.format(e))
                log.info('use default.conf: {0}'.format("default"))
                default_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "default.conf")
                with open(default_file, "r") as fp:
                    res = json.load(fp)["data"]['api_gateway'][0]

            # get domain
            try:
                rds_domain = res['api-logic-rds_dns_name']
                bcc_domain = res['bcc_domain']
                region = res['region']
            except Exception as e:
                log.warning('get ednpoint failed:{0}'.format(e))
            # get paas as sk
            try:
                account = config.get('ic', 'iam_url')
                result = requests.get(account)
                res = json.loads(result.text)['data']['iam_nginx'][0]
            except Exception as e:
                log.warning('get ic failed: {0}'.format(e))
                log.info('use default.conf: {0}'.format("default"))
                default_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "default.conf")
                with open(default_file, "r") as fp:
                    res = json.load(fp)["data"]['iam_nginx'][0]
            try:
                ak = res['paas_user_1_ak']
                sk = res['paas_user_1_sk']
            except Exception as e:
                log.warning('get account failed:{0}'.format(e))

            # 配置文件重置
            try:
                config.set("account", "ak_normal", ak)
                config.set("account", "sk_normal", sk)
                config.set("rds", "rds_host", rds_domain + ':8680')
                config.set("rds", "rds_endpoint", rds_domain + ':8680')
                config.set("vpc", "vpc_host", bcc_domain + ':8680')
                config.set("vpc", "vpc_endpoint", bcc_domain + ':8680')
                config.set("region", "region", region)
                config.write(open(domain_file, 'w'))
                config.read(domain_file)
                rds_host = config.get('rds', 'rds_host')
                rds_endpoint = config.get('rds', 'rds_endpoint')

                vpc_host = config.get('vpc', 'vpc_host')
                vpc_endpoint = config.get('vpc', 'vpc_endpoint')

                ak = config.get('account', 'ak_normal')
                sk = config.get('account', 'sk_normal')
                log.info('rds_host: {0}'.format(rds_host))
                log.info('rds_endpoint: {0}'.format(rds_endpoint))
                log.info('vpc_host: {0}'.format(vpc_host))
                log.info('vpc_endpoint: {0}'.format(vpc_endpoint))
                log.info('ak: {0}'.format(ak))
                log.info('sk: {0}'.format(sk))
            except Exception as e:
                log.warning('can not get account、 host:{0}'.format(e))
        else:
            # 可手动适配环境
            config.read(file)
            rds_host = config.get('rds', 'rds_host')
            rds_endpoint = config.get('rds', 'rds_endpoint')

            vpc_host = config.get('vpc', 'vpc_host')
            vpc_endpoint = config.get('vpc', 'vpc_endpoint')

            ak = config.get('account', 'ak_normal')
            sk = config.get('account', 'sk_normal')
            log.info('rds_host: {0}'.format(rds_host))
            log.info('rds_endpoint: {0}'.format(rds_endpoint))
            log.info('vpc_host: {0}'.format(vpc_host))
            log.info('vpc_endpoint: {0}'.format(vpc_endpoint))
            log.info('ak: {0}'.format(ak))
            log.info('sk: {0}'.format(sk))

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

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

    def __init__(self):
        # 判断是否执行过初始化动作
        if Config.init_flag:
            return
        # 如果没有执行过,在执行初始化动作
        # 修改类属性的标记
        Config.init_flag = True


if __name__ == '__main__':
    print(Config().rds_host)
    print(Config().rds_host)
    print(Config().ak)
    print(Config().sk)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值