[Python] 实现单例模式的四种方式及单例模式日志

0. 前情提要

我们知道类 __init__ 方法,还应该知道类的 __new__方法,和元类的 __call__ 方法。这三个方法的执行顺序是:

元类的 __call__ 方法  ==>   类的 __new__方法  == >  类的 __init__ 方法

只要利用三者的执行顺序,我们就可以在创建实例对象的时候,判断实例对象是否已经创建,如果是,则返回已经创建好的实例对象,否则新建一个实例对象并返回。这样就可以实现单例模式了。

如果你还不知道 cls__new____call__ ,那么你可能无法理解它们在单例模式的应用。可以参考:

[Python] 深入理解 self、cls、__call__、__new__、__init__、__del__、__str__、__class__、__doc__等

1. 使用__new__实现单例模式

利用运行 __init__ 方法,初始化实例之前,会运行类的 __new__方法,我们可以在 __new__ 方法判断类是否已经创建过实例对象,如果是,则返回已经创建好的实例对象,否则新建一个实例对象并返回。

class Singleton(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, "_instance"):   # hasatter() 判断一个对象是否有某个属性
            cls._instance = super().__new__(cls, *args, **kwargs)
        return cls._instance

print(Singleton() is Singleton())   # True

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

class Singleton(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, "_instance"):   # hasatter() 判断一个对象是否有某个属性
            cls._instance = super().__new__(cls)
        return cls._instance

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

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

a1, a2 = Singleton('a1'), Singleton('a2')
a1.print_name()     # a2
a2.print_name()     # a2

我们发现 a1 对象的 name 被覆盖掉了。

2. 使用__call__方法实现单例模式

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

class SingletonType(type):
    _instances = {}
    
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(SingletonType, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class A(metaclass=SingletonType):
    pass

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

还可以这样:

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

3. 使用装饰器实现单例模式

用装饰器来控制调用__call__方法。

def singleton(cls):
    instances = {}

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

    return _singleton

@singleton
class A:
    pass

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

4. 模块单例

Python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。

# single.py
class Singleton: 
	pass

singleton = Singleton()


# other.py
from single import singleton

5. 简单总结

除了模块单例外,其他几种模式的本质都是通过设置中间变量,来判断类是否已经拥有实例对象。区别就是中间变量或设置在元类中,或封装在函数中,或设置在类中作为静态变量。

中间变量的访问和更改存在线程安全的问题:在开启多线程模式的时候需要加锁处理。

6. 使用单例模式实现的日志模块

"""
我们的目的是借助单例模式,只生成一个logger。
当日志文件已经存在时,默认以追加方式写入。
"""
import logging
import os
import time
import sys


class SingletonType(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(SingletonType, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


class Logger(metaclass=SingletonType):
    def __new__(cls, log_file=None, file_output=True, standard_output=True):
        """
        1. Logger的__new__只在创建第一个实例的时候被调用。
        2. 我们要返回的不是Logger类实例,而是使用内置logging模块创建的实例。
        """
        # 日志文件的路径
        if log_file is None:
            # 文件的当前目录
            current_path = os.path.dirname(os.path.realpath(__file__))
            current_time = time.strftime("%Y-%m-%d-%H%M%S", time.localtime())
            log_file = os.path.join(current_path, 'log_output', f'{current_time}.txt')
            os.makedirs(os.path.dirname(log_file), exist_ok=True)

        cls.logger = logging.getLogger(__name__)
        formater = logging.Formatter('%(asctime)s %(name)s [%(filename)s %(lineno)s] %(message)s')
        # 如果既不使用标准输出,也不使用日志文件输出,则选择标准输出
        if not file_output and not standard_output:
            standard_output = True
        if standard_output:
            # 标准输出
            standard_out = logging.StreamHandler(sys.stdout)
            standard_out.setFormatter(formater)
            cls.logger.addHandler(standard_out)    # 添加标准输出
        if file_output:
            # 日志文件输出
            file_out = logging.FileHandler(log_file, encoding='utf-8')
            file_out.setFormatter(formater)
            cls.logger.addHandler(file_out)    # 添加文件输出
            cls.logger.setLevel(logging.INFO)
        return cls.logger


if __name__ == '__main__':
    logger = Logger("Logger1")  # 只第一个名称有效
    logger2 = Logger("Logger2")
    logger3 = Logger("Logger3")
    logger.info("This is a info")
    print(logger is logger2, logger2 is logger3)
    print(not False and not False)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值