Python设计模式-单例模式

img

设计思想

通过上面的介绍,我们可以知道单例模式最重要的就是要保证一个类只有一个实例并且这个类易于被访问,那么要怎么做才能保证一个类具有一个实例呢?一个全局变量使得一个对象可以被访问,但是这样做却不能防止你实例化多个对象。 一个更好的办法就是,让该类自身负责保存它的唯一实例instance。并且这个类保证没有其他的实例可以被创建。 怎样保证一个对象的唯一性总结如下:

1.构造器私有化:为了避免其它程序过多的建立该类的对象,先禁止其它程序建立该类对象实例
2.单例对象公开化:为了方便其它程序访问该类的对象,只好在本类中自定义一个对象,由1可知该对象是static的,并对外提供访问方式。

单例定义

  • 单例设计模式保证一个类只有一个实例。
  • 要提供一个访问该类对象实例的全局访问点。

分类:

1.饿汉式这种方式加载类对象,我们称作:预先加载方式。 多线程没有问题。

2.懒汉式这种方式加载类对象,我们称作:延迟加载方式。多线程会出现线程安全问题。

多线程模式下懒汉式与饿汉式区别:https://cloud.tencent.com/developer/article/1414936

使用场景

  1. 配置文件
    服务器中的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象,再通过这个单例对象获取这些配置信息。从而简化了在比较复杂的环境下配置管理。
  2. 日志记录
  3. 数据库操作
  4. 打印机后台处理程序
  5. Django源码
    https://zhuanlan.zhihu.com/p/389035970
    1. admin,Django的admin模块
    2. site,conf.settings

实现方式

除了模块导入外,其他几种单例模式的实现方式本质都是通过设置中间变量,来判断类是否已经拥有实例对象。区别就是中间变量或设置在元类中,或封装在函数中,或设置在类中作为静态变量。中间变量的访问和更改存在线程安全的问题:在开启多线程模式的时候需要加锁处理。Python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。中间变量的访问和更改存在线程安全的问题:在开启多线程模式的时候需要加锁处理。

模块导入单例模式

single.py

class Singleton: 
	pass

singleton = Singleton()

other.py

from single import singleton

其他场景单例模式

Python实现单例模式的方式:本质上就三种实现方式:

  • new() 创建类对象的实例,init()为其个例,即懒汉式
  • __call__元类方式实现,继承元类,
  • decorator装饰器方式实现,来构建实例化对象是否为空检查,用字典存储其值;

以下详细介绍:

new()实现

new() 是一种负责创建类实例的静态方法(实际上最根本的由元类type控制着对象的实例化),它无需使用 staticmethod 装饰器修饰,且该方法会优先 init() 初始化方法被调用。init()是负责创建实例属性的静态方法。一般情况下,覆写 new() 的实现将会使用合适的参数调用其超类的 super().new(),并在返回之前修改实例。例如:

class demoClass:
    instances_created = 0
    
    def __new__(cls,*args,**kwargs):
        print("__new__():",cls,args,kwargs)
        instance = super().__new__(cls)
        instance.number = cls.instances_created
        cls.instances_created += 1
        return instance
    
    def __init__(self,attribute):
        print("__init__():",self,attribute)
        self.attribute = attribute
        
        
test1 = demoClass("abc")
test2 = demoClass("xyz")
print(test1.number,test1.instances_created)
print(test2.number,test2.instances_created)
>>>
# __new__(): <class '__main__.demoClass'> ('abc',) {}
# __init__(): <__main__.demoClass object at 0x0000026FC0DF8080> abc
# __new__(): <class '__main__.demoClass'> ('xyz',) {}
# __init__(): <__main__.demoClass object at 0x0000026FC0DED358> xyz
# 0 2
# 1 2

继承的方式实现,其方法实现单例模式的基本原理如下所示:

class Singleton(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
        return cls._instance


class Foo(Singleton):  # 单例类
    a = 1


foo = Foo()
print('test1 obj is: ', foo)

foo = Foo()
print('test2 att is: ', foo)
# >>>
# test1 obj is:  <__main__.Foo object at 0x7f81ea8e1ee0>
# test2 att is:  <__main__.Foo object at 0x7f81ea8e1ee0>

或者不使用继承得方式实现:

注意:new__方法无法避免触发__init(),初始的成员变量会进行覆盖。

class Singleton:
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super(Singleton, cls).__new__(cls)
        return cls._instance

    def __init__(self, name=None):
        self.name = name

    def print_name(self):
        print(self.name)


s = Singleton('10')
print(s)
print(s.print_name())

s1 = Singleton('20')
print(s1)
print(s1.print_name())
print(s.print_name())
# >>>
# <__main__.Singleton object at 0x7f9dc6966ee0>
# 10
# <__main__.Singleton object at 0x7f9dc6966ee0>
# 20
# 20		# s的成员变量10被s1的20覆盖了

new() 通常会返回该类的一个实例,但有时也可能会返回其他类的实例,如果发生了这种情况,则会跳过对__init__()方法的调用。而在某些情况下(比如需要修改不可变类实例(Python 的某些内置类型)的创建行为),利用这一点会事半功倍。比如:

class NonZero(int):
    def __new__(cls, value):
        return super(NonZero, cls).__new__(cls, value) if value != 0 else None

    def __init__(self, skipped_value):
        # 此例中会跳过此方法
        print("__init__()")
        super().__init__()


print(type(NonZero(-12)))
print(type(NonZero(0)))
# >>>
# __init__()
# <class '__main__.NonZero'>
# <class 'NoneType'>		# 这里没有再次运行init方法,而是直接跳过

那么,什么情况下使用 new()呢?答案很简单,在__init__()不够用的时候。

例如,前面例子中对 Python 不可变的内置类型(如 int、str、float 等)进行了子类化,这是因为一旦创建了这样不可变的对象实例,就无法在__init__()方法中对其进行修改。

有些读者可能会认为,new() 对执行重要的对象初始化很有用,如果用户忘记使用 super(),可能会漏掉这一初始化。虽然这听上去很合理,但有一个主要的缺点,即如果使用这样的方法,那么即便初始化过程已经是预期的行为,程序员明确跳过初始化步骤也会变得更加困难。不仅如此,它还破坏了__init__()中执行所有初始化工作”的潜规则。

注意,由于__new__()不限于返回同一个类的实例,所以很容易被滥用,不负责任地使用这种方法可能会对代码有害,所以要谨慎使用。一般来说,对于特定问题,最好搜索其他可用的解决方案,最好不要影响对象的创建过程,使其违背程序员的预期。比如说,前面提到的覆写不可变类型初始化的例子,完全可以用工厂方法(一种设计模式)来替代。

Python中大量使用 new() 方法且合理的,就是 MetaClass 元类。有关元类的介绍,可阅读《Python MetaClass元类》一节。

init()实现

为__new__的个例,即懒汉模式。

懒汉式的实例化,例如在导入模块的时候,我们可能会无意中创建一个对象,但当时根本用不到它。懒汉式实例化能够确保实际需要时才创建实例化对象,所以,懒汉式实例化是一种节约资源并仅在需要时才创建它们的方式。

在下面的示例代码中,执行s = Singleton()时候并未创建,只有在Singleton.get_instance()才实际创建实例化对象,调用__new__方法构建。这里本质上还是调用上一个的__new__方法来构建对象的,即Singleton()是调用__new__()方法构建对象。

class Singleton:
    _instance = None

    def __init__(self):
        if not self._instance:
            print('__init__ method called')
        else:
            print('Instance already created: ', self.get_instance())

    @classmethod
    def get_instance(cls):
        if not cls._instance:
            # Singleton()应该是调用__new__()方法构建对象
            cls._instance = Singleton()
        return cls._instance


s = Singleton()
print(Singleton.get_instance())
s1 = Singleton()
# >>>
# __init__ method called
# __init__ method called
# <__main__.Singleton object at 0x7f23fdf0ce80>
# Instance already created:  <__main__.Singleton object at 0x7f23fdf0ce80>

__call__实现

简单点说,就是type元类的__call__ 方法在类的 new 方法和 init 方法之前执行。

class SingletonType(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        # _instances这里区别与装饰器的对象方法,为类方法,需要cls.
        if cls not in cls._instances:
            cls._instances[cls] = super(SingletonType, cls).__call__(*args, **kwargs)
        return cls._instances[cls]
    
    # 或者
    # def __call__(cls, *args, **kwargs):
    #     return type.__call__(cls, *args, **kwargs)


class A(metaclass=SingletonType):
    pass


print(A() is A())
# >>>
# True

问题:

# *args, **kwargs与cls, *args, **kwargs的区别,为什么?
# 自己call不需要传入自己,即cls,其他人如type来call需要传入自己cls
super(SingletonType, cls).__call__(*args, **kwargs)

def __call__(cls, *args, **kwargs):
    return type.__call__(cls, *args, **kwargs)
    

Traceback (most recent call last):
  File "/home/kali/oadps/common/test_logger.py", line 217, in <module>
    print(A() is A())
  File "/home/kali/oadps/common/test_logger.py", line 208, in __call__
    cls._instances[cls] = super(SingletonType, cls).__call__(cls, *args, **kwargs)
TypeError: A() takes no arguments
    

class type(object):
    """
    type(object) -> the object's type
    type(name, bases, dict, **kwds) -> a new type
    """
    def mro(self, *args, **kwargs): # real signature unknown
        """ Return a type's method resolution order. """
        pass

    def __call__(self, *args, **kwargs): # real signature unknown
        """ Call self as a function. """
        pas

或者这样实现:(这里看不太懂元类的逻辑)

class Singleton(type):
    def __init__(self, *args, **kwargs):
        self.__instance = None
        super().__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            self.__instance = super().__call__(*args, **kwargs)
        return self.__instance


class A(metaclass=Singleton):
    pass


print(A() is A())
# >>>
# True

decorator实现

用装饰器singleton方法去装饰MyFun类,装饰器实现基本逻辑如下:

def singleton(cls, *args, **kwargs):
    instances = {}

    def _singleton():
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)	# 用装饰器来控制调用__call__方法
        return instances[cls]

    return _singleton


@singleton
class MyFun:
    a = 1

    def __init__(self, x=0):
        self.x = x


print(MyFun() is MyFun())   # True

单例模式日志

采用装饰器singleton实现日志实例对象唯一性,全部模块代码如下:

# -*- coding:utf-8 -*-
import os
import time
import logging


def singleton(cls, *args, **kwargs):
    """
    Singleton pattern decorator used by logger function.
    """
    instances = {}

    def _singleton():
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]

    return _singleton


class PathFileHandler(logging.FileHandler):
    """
    Handler for file and path.
    """

    def __init__(self, path, filename, mode='a', encoding=None, delay=False, errors=None):
        if not os.path.exists(path):
            os.mkdir(path)
        self.baseFilename = os.path.join(path, filename)
        self.mode = mode
        self.encoding = encoding
        self.delay = delay
        self.errors = errors

        if delay:
            logging.Handler.__init__(self)
            self.stream = None
        else:
            logging.StreamHandler.__init__(self)

        logging.StreamHandler.__init__(self, self._open())


@singleton
class MyLogger:
    """
    Custom my own logger class.
    """
    level_relations = {
        'debug': logging.DEBUG,
        'info': logging.INFO,
        'warning': logging.WARNING,
        'error': logging.ERROR,
        'critical': logging.CRITICAL
    }

    def __init__(self, filename='{}.logs'.format(time.strftime("%Y-%m-%d_%H%M%S", time.localtime())), level='debug',
                 log_dir='logs', fmt='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s'):
        # create Logger、set level、set Formatter
        self.logger = logging.getLogger(filename)
        self.logger.setLevel(self.level_relations.get(level))
        format_str = logging.Formatter(fmt)
        self.directory = os.path.join(os.getcwd(), log_dir)

        if not self.logger.handlers:
            # output to terminal
            stream_handler = logging.StreamHandler()
            stream_handler.setLevel(logging.INFO)
            stream_handler.setFormatter(format_str)

            # output to file
            file_handler = PathFileHandler(path=self.directory, filename=filename, mode='a')
            file_handler.setFormatter(format_str)

            # add the Handler
            self.logger.addHandler(stream_handler)
            self.logger.addHandler(file_handler)


# Import this logger attribute in your model, then you can use logger.info or
# logger.warnning ... to output logs in your terminal and log files.
logger = MyLogger().logger
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值