likes
comments
collection
share

Python 实现单例模式的几种常见方式

作者站长头像
站长
· 阅读数 75

Python 实现单例模式的几种常见方式

单例模式(Singleton Pattern)是一种常用的开发设计模式,它的主要目的就是确保只有一个实例对象的存在。换句话说,当一个类的功能比较单一,只需要一个实例对象就可以完成需求时,就可以使用单例模式来节省内存资源。

1. 模块实现单例

python模块在第一导入时,会生成.pyc编译文件,当再次导入时,就直接加载.pyc编译文件,而不会再次执行模块代码,所以可以说模块就是一个天然的单例模式。利用模块这个特性,只需把相关函数和数据定义在一个模块中,将它导入到其他模块使用就可以达到单例模式的效果。

# person.py
class Person(object):
    def __init__(self,name, age):
        self.name = name
        self.age = age
person_instance = Person('张三',18)
# 示例代码
from person import person_instance

if __name__ == '__main__':
    p1 = person_instance
    p2 = person_instance
    p2.name = '李四'
    p2.age = 22
    print(p1)
    print(p2)
    print(p1 == p2)

# 输出结果
<person.Person object at 0x00000270A4CB26D0>
<person.Person object at 0x00000270A4CB26D0>
True

2. 装饰器实现单例

装饰器的作用是给函数或者类扩展功能,所以可以利用装饰器来让类只生成一个实例对象。在装饰器里定义一个字典,用来存放类的实例。当第一次创建时,将这个实例保存到这个字典中。然后以后每次创建对象的时候,都去这个字典中查找是否已经存在。如果存在,就直接返回这个实例对象,否则,就创建实例并保存到字典中。

  • 使用函数的方式创建装饰器
# 示例代码
def singleton(cls):
    _instance = {}
    def decorated(*args, **kwargs):
        if cls not in _instance:
            _instance[cls] = cls(*args, **kwargs)
        return _instance[cls]
    return decorated


@singleton
class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
        

if __name__ == '__main__':
    p1 = Person('张三',18)
    p2 = Person('李四',22)
    print(p1)
    print(p2)
    print(p1==p2)
# 输出结果
<__main__.Person object at 0x0000024E3A9826D0>
<__main__.Person object at 0x0000024E3A9826D0>
True
  • 使用类的方式创建装饰器
# 示例代码
class Singleton(object):
    def __init__(self,cls):
        self._cls = cls
        self._instance = {}

    def __call__(self, *args):
        if self._cls not in self._instance:
            self._instance[self._cls] = self._cls(*args)
        return self._instance[self._cls]

@Singleton
class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
        

if __name__ == '__main__':
    p1 = Person('张三',18)
    p2 = Person('李四',22)
    print(p1)
    print(p2)
    print(p1==p2)
# 输出结果
<__main__.Person object at 0x000001E9CB432760>
<__main__.Person object at 0x000001E9CB432760>
True

3. 使用类方法实现单例

当使用类直接创建实例对象的时候,创建的并不是单例对象,那么可以在类中定义一个类方法来实现单例模式,主要思路就是会去判断类是否有_instance这个属性,如果有则直接返回这个实例,没有则创建实例。

# 示例代码
class Person(object):
    def __init__(self,name, age):
        self.name = name
        self.age = age

    @classmethod
    def get_instance(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = cls(*args, **kwargs)
        return cls._instance


if __name__ == '__main__':
    p1 = Person.get_instance('张三',18)
    p2 = Person.get_instance('李四',22)
    print(p1)
    print(p2)
    print(p1 == p2)
# 输出结果
<__main__.Person object at 0x0000023233D10430>
<__main__.Person object at 0x0000023233D10430>
True

4. 使用__new__方法实现单例

Python中一个对象实例化过程大致为:首先执行类的__new__方法,如果没有,默认会调用父类的__new__方法,返回一个实例化对象。然后在调用__init__方法对此对象进行初始化。我们可以利用这个过程,实现单例模式。在类的__new__方法中,判断是否存在实例,如果存在,直接返回实例,否则,创建一个实例。这样就能够达到单例模式效果了。

# 示例代码
class Person(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super().__new__(cls)
        return cls._instance
    
    def __init__(self,name, age):
        self.name = name
        self.age = age


if __name__ == '__main__':
    p1 = Person('张三',18)
    p2 = Person('李四',22)
    print(p1)
    print(p2)
    print(p1 == p2)
# 输出结果
<__main__.Person object at 0x0000022188C92760>
<__main__.Person object at 0x0000022188C92760>
True

5. 使用元类metaclass实现单例

Python中元类是控制类创建的类,所以可以在创建类的时候通过元类来创建类,在元类的__call__方法中,判断是否存在实例,如果存在,直接返回实例,否则,创建一个实例,使其只存在一个实例对象。

# 示例代码
class Singleton(type):
    def __call__(cls, *args, **kwargs):
        if not hasattr(cls, "_instance"):
            cls._instance = super().__call__(*args, **kwargs)
        return cls._instance


class Person(metaclass=Singleton):
    def __init__(self,name, age):
        self.name = name
        self.age = age


if __name__ == '__main__':
    p1 = Person('张三',18)
    p2 = Person('李四',22)
    print(p1)
    print(p2)
    print(p1 == p2)
# 输出结果
<__main__.Person object at 0x000001D58F142760>
<__main__.Person object at 0x000001D58F142760>
True

6. 存在的问题

在使用类方法、__new__方法、元类来实现单例时,在单线程下是安全的。但是如果遇到多个线程同时创建这个类的实例的时候就会出现问题。

  • 多线程下存在同时创建多个实例的现象,属于线程安全问题

当加入多线程去创建实例对象的时候,如果执行速度够快还不会出现影响,当执行速度不够快的时候,一个线程去创建实例然后拿到了_instance这个属性去判断,其他的线程也可能会拿到_instance这个属性,发现并没有实例存在。此时,这两个线程就会同时创建一个实例,造成同时创建多个实例的现象,那么单例模式自然也就失效了。

# 示例代码
import threading
import time

class Person(object):
    def __init__(self,name, age):
        self.name = name
        self.age = age
        time.sleep(1)

    @classmethod
    def get_instance(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = cls(*args, **kwargs)
        return cls._instance


def task(arg):
    p = Person.get_instance('张三',18)
    print(p)


if __name__ == '__main__':
    for i in range(10):
        t = threading.Thread(target=task, args=[i,])
        t.start()
# 输出结果
<__main__.Person object at 0x00000262879661F0>
<__main__.Person object at 0x0000026287952CA0>
<__main__.Person object at 0x0000026287966520>
<__main__.Person object at 0x0000026287952940>
<__main__.Person object at 0x0000026287966AC0>
<__main__.Person object at 0x0000026287966DC0>
<__main__.Person object at 0x00000262879665B0>
<__main__.Person object at 0x000002628796E400>
<__main__.Person object at 0x0000026287952910>
<__main__.Person object at 0x000002628796E100>
  • 解决办法是加线程锁

在获取对象属性_instance的时候加锁,让每一个线程使用完后才释放它,接着下一个线程继续运行程序,就可以解决同时获取对象并创建不同的实例的问题。

# 示例代码-类方法实现单例(改进版)
import threading
import time

class Person(object):
    _lock = threading.Lock()
    def __init__(self,name, age):
        self.name = name
        self.age = age
        time.sleep(1)

    @classmethod
    def get_instance(cls, *args, **kwargs):
        with cls._lock:
            if not hasattr(cls, '_instance'):
                cls._instance = cls(*args, **kwargs)
        return cls._instance


def task(arg):
    p = Person.get_instance('张三',18)
    print(p)


if __name__ == '__main__':
    for i in range(10):
        t = threading.Thread(target=task, args=[i,])
        t.start()
# 输出结果
<__main__.Person object at 0x000001C0664728E0>
<__main__.Person object at 0x000001C0664728E0>
<__main__.Person object at 0x000001C0664728E0>
<__main__.Person object at 0x000001C0664728E0>
<__main__.Person object at 0x000001C0664728E0>
<__main__.Person object at 0x000001C0664728E0>
<__main__.Person object at 0x000001C0664728E0>
<__main__.Person object at 0x000001C0664728E0>
<__main__.Person object at 0x000001C0664728E0>
<__main__.Person object at 0x000001C0664728E0>
# 示例代码-__new__方法实现单例(改进版)
import threading
import time

class Person(object):
    _lock = threading.Lock()
    
    def __new__(cls, *args, **kwargs):
        time.sleep(1)
        with cls._lock:
            if not hasattr(cls, '_instance'):
                cls._instance = super().__new__(cls)
        return cls._instance
    
    def __init__(self,name, age):
        self.name = name
        self.age = age


def task(arg):
    p = Person('张三',18)
    print(p)


if __name__ == '__main__':
    for i in range(10):
        t = threading.Thread(target=task, args=[i,])
        t.start()
# 示例代码-元类实现单例(改进版)
import threading
import time

class Singleton(type):
    _lock = threading.Lock()
    
    def __call__(cls, *args, **kwargs):
        time.sleep(1)
        with cls._lock:
            if not hasattr(cls, "_instance"):
                cls._instance = super().__call__(*args, **kwargs)
        return cls._instance


class Person(metaclass=Singleton):
    def __init__(self,name, age):
        self.name = name
        self.age = age


def task(arg):
    p = Person('张三',18)
    print(p)


if __name__ == '__main__':
    for i in range(10):
        t = threading.Thread(target=task, args=[i,])
        t.start()

以上是整理的Python中实现单例模式的几种方式,希望对大家学习Python有所帮助。

转载自:https://juejin.cn/post/7142018510678556703
评论
请登录