我们知道类 __init__
方法,还应该知道类的 __new__
方法,和元类的 __call__
方法。这三个方法的执行顺序是:
元类的
__call__
方法 ==> 类的__new__
方法 == > 类的__init__
方法
只要利用三者的执行顺序,我们就可以在创建实例对象的时候,判断实例对象是否已经创建,如果是,则返回已经创建好的实例对象,否则新建一个实例对象并返回。这样就可以实现单例模式了。
如果你还不知道 cls
、__new__
、__call__
,那么你可能无法理解它们在单例模式的应用。可以参考:
[Python] 深入理解 self、cls、__call__、__new__、__init__、__del__、__str__、__class__、__doc__等
利用运行 __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 被覆盖掉了。
简单点说,就是元类的__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
用装饰器来控制调用__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
Python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。
# single.py
class Singleton:
pass
singleton = Singleton()
# other.py
from single import singleton
除了模块单例外,其他几种模式的本质都是通过设置中间变量,来判断类是否已经拥有实例对象。区别就是中间变量或设置在元类中,或封装在函数中,或设置在类中作为静态变量。
中间变量的访问和更改存在线程安全的问题:在开启多线程模式的时候需要加锁处理。
"""
我们的目的是借助单例模式,只生成一个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)