元类与单例模式

一、元类

元类是什么

有一句话在编程语言圈中流行,python一切皆对象。在之前初学面向对象的时候,我们举过例子,之前使用的函数都是通过类造出来的对象。但是我们没有思考过,既然一切皆对象,那么类是否也是一个对象?如果他是一个对象,那么他又是被哪个类造出来的?

这个就是今天要学习的内容,元类,即类这个对象的类.

我们查看某个对象属于哪个类,是利用type,那么我们可以使用相同方法,查看类的类是什么。

class MyClass:
    def __init__(self,name,age):
        self.name = name
        self.age = age
obj = MyClass('yang',18)
print(type(obj))
print(type(MyClass))
----------------------
<class '__main__.MyClass'>
<class 'type'>  # 此处可知元类是type

默认的元类是type,在默认情况下,我们用class关键字定义的类都是由type产生。

模拟class关键字

对于一个类来说,组成部分有三个,类名,基类,类体代码。在下面,我们模拟class关键字产生一个类。

# 1、先拿到一个类名
class_name = "Teacher"

# 2、然后拿到类的父类
class_bases = (object,)  # 如果未继承类,则默认为object

# 3、再运行类体代码,将产生的名字放到名称空间中
class_dic = {}
class_body = """
school = 'qinghua'

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

def say(self):
    print('%s says welcome' % self.name)
"""
exec(class_body,{},class_dic)  # exec 执行储存在字符串或文件中的Python语句,相比于eval,exec可以执行更复杂的 Python 代码。
# print(class_dic)

# 4、调用元类(传入类的三大要素:类名、基类、类的名称空间)得到一个元类的对象,然后将元类的对象赋值给变量名Teacher,Teacher就是我们用class自定义的那个类
MyClass = type(class_name,class_bases,class_dic)

自定义元类

既然知道了class关键字是如何创建类的,那么我们就清楚了类的实现过程,那么我们可以自定义一个元类了,自定义元类的好处有很多,在下面的例子中你可以看到.

基本格式

class Mymeta(type):  # 必须要继承type
    pass

class MyClass(metaclass=Mymeta):  # 指明利用自定义元类创建类
    pass

obj = MyClass()
print(obj)
----------------------
<__main__.MyClass object at 0x7fd442328c70>

在自定义元类中,我们可以加以对类创建的控制,如名字必须大写开头,必须要有类的注释,把利用此类生成的对象的所有属性和方法都设置为私有等等。在这里我们使用到了raise,主动抛出异常。

import re

class Mymeta(type):  # 只有继承了type类的类才是自定义的元类
    def __init__(self, class_name, class_bases, class_dic):
        if not re.match("[A-Z]", class_name):
            raise BaseException("类名必须用驼峰体")

        if len(class_bases) == 0:
            raise BaseException("至少继承一个父类")

        # print("文档注释:",class_dic.get('__doc__'))
        doc=class_dic.get('__doc__')
        if not (doc and len(doc.strip()) > 0):
            raise BaseException("必须要有文件注释,并且注释内容不为空")

class Teacher(object,metaclass=Mymeta):
    """
    文档注释必须有,不然要抛出异常
    """

    addr = 'Shanghai'

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

    def say(self):
        print('%s says welcome to the oldboy to learn Python' % self.name)
        
        
obj = Teacher('yang',18)
print(obj)
-----------------------------
<__main__.Teacher object at 0x7fb8c4428b80>

一个对象加括号,会触发魔术方法__call__方法,既然类作为一个对象,肯定在加括号运行时候也触发了这个方法,所以我们可以推测在type中必定有__call__方法,我们也可以自己写这个方法,来控制类加括号运行时发生的事情。比如,将一个在实例化的时候就把所有的属性和方法都设置为私有。

import re

class Mymeta(type):  # 只有继承了type类的类才是自定义的元类
    def __init__(self, class_name, class_bases, class_dic):
        if not re.match("[A-Z]", class_name):
            raise BaseException("类名必须用驼峰体")

        if len(class_bases) == 0:
            raise BaseException("至少继承一个父类")

        # print("文档注释:",class_dic.get('__doc__'))
        doc = class_dic.get('__doc__')

        if not (doc and len(doc.strip()) > 0):
            raise BaseException("必须要有文件注释,并且注释内容不为空")

    def __call__(self, *args, **kwargs):
        # 1、先创建一个老师的空对象,利用__new__魔术方法,利用父类创建对象
        tea_obj = object.__new__(self)
        # 2、调用老师类内的__init__函数,然后将老师的空对象连同括号内的参数的参数一同传给__init__
        self.__init__(tea_obj, *args, **kwargs)
        # 利用字典生成式,直接一行代码解决私有化
        tea_obj.__dict__ = {"_%s__%s" %(self.__name__,k): v for k, v in tea_obj.__dict__.items()}
        # 3、将初始化好的老师对象赋值给变量名res
        return tea_obj


class Teacher(object, metaclass=Mymeta):
    """
    必须写注释不然要报错
    """
    school = 'MIT'
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def say(self):
        print('%s says welcome to the oldboy to learn Python' % self.name)


res = Teacher('yang', 18)
print(res.__dict__)
----------------------------------
{'_Teacher__name': 'yang', '_Teacher__age': 18}

二、多线程下的单例

1、多线程下数据安全的单例(重要)

使用threading.Lock方法,把创建对象的线程串行化。在学习过程中,博主发现添加了time.sleep(1)这句后,多线程下无法正常实现单例,由此引发了下面与chatgpt的询问过程

def synchronized(func):
    func.__lock__ = threading.Lock()

    def lock_func(*args, **kwargs):
        with func.__lock__:
            return func(*args, **kwargs)

    return lock_func


class Singleton(object):
    instance = None

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


    @synchronized
    def __new__(cls, *args, **kwargs):
        # 关键在于这,每一次实例化的时候,我们都只会返回这同一个instance对象
        if not cls.instance:
            time.sleep(1)
            cls.instance = super(Singleton, cls).__new__(cls)
        return cls.instance



def task(name):
    ob = Singleton(name)
    print(ob)


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

以下为关于线程安全的chatgpt对话内容:


**王寄鱼:** __new__方法的使用

**Ai.小助手:** `__new__` 方法是在创建一个新对象时调用的特殊方法。它是一个静态方法,与其他类方法不同,它不会自动传递类实例作为第一个参数(通常是 `self`)。`__new__` 方法的主要作用是控制对象的创建过程,可以在对象创建之前进行一些自定义的操作。

下面是一个简单的示例,演示了 `__new__` 方法的使用:

```python
class MyClass:
    def __new__(cls, *args, **kwargs):
        print("Creating a new instance")
        instance = super().__new__(cls)
        return instance

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

# 创建对象
obj = MyClass("example")
```

在上面的示例中,我们定义了一个 `MyClass` 类,并重写了 `__new__` 方法。在 `__new__` 方法内部,我们打印了一条消息,表示正在创建一个新实例。然后,我们使用 `super().__new__(cls)` 调用父类的 `__new__` 方法来创建实例。最后,我们将创建的实例返回。

需要注意的是,`__new__` 方法返回的是一个对象实例,而 `__init__` 方法则是用来初始化这个实例。在上面的示例中,我们还定义了 `__init__` 方法来初始化 `name` 属性。

总结一下,`__new__` 方法用于控制对象的创建过程,而 `__init__` 方法用于初始化对象的属性。

**王寄鱼:** 以下代码在多线程情况为何依然数据安全?

class Singleton(object):
    instance = None

    def __init__(self, name):
        self.name = name
        time.sleep(1)


    # @synchronized
    def __new__(cls, *args, **kwargs):
        # 关键在于这,每一次实例化的时候,我们都只会返回这同一个instance对象
        if not cls.instance:
            cls.instance = super(Singleton, cls).__new__(cls)
        return cls.instance



def task(name):
    ob = Singleton(name)
    print(ob)


for i in range(5):
    t = threading.Thread(target=task,args=[i,])
    t.start()

**Ai.小助手:** 在给定的代码中,虽然存在多线程,但数据仍然是安全的,这是因为使用了单例模式来创建对象。

在单例模式中,只有一个实例对象可以被创建和访问。在这个例子中,`Singleton` 类的 `__new__` 方法确保只有一个实例被创建并返回。具体来说,`instance` 属性在第一次实例化时被赋值为 `None`,然后在 `__new__` 方法中检查 `instance` 是否为 `None`,如果是,则使用 `super().__new__(cls)` 创建一个新的实例,并将其赋值给 `instance`。之后,无论多少线程尝试实例化 `Singleton` 类,都会返回同一个实例。

因为只有一个实例被创建和共享,所以在多线程环境下,每个线程都会得到相同的实例对象。这就保证了数据的安全性,因为所有线程都在访问和操作同一个对象。

需要注意的是,单例模式并不一定适用于所有情况,特别是在多线程环境下。在某些情况下,可能需要采取其他措施来确保数据的安全性,例如使用锁机制或其他并发控制手段。然而,在给定的代码中,由于使用了单例模式,因此数据是安全的。

**王寄鱼:** 为何仅仅调整了time.sleep(1)的位置,就造成了数据不安全?比如一下代码:

class Singleton(object):
    instance = None

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



    # @synchronized
    def __new__(cls, *args, **kwargs):
        # 关键在于这,每一次实例化的时候,我们都只会返回这同一个instance对象
        if not cls.instance:
            time.sleep(1)
            cls.instance = super(Singleton, cls).__new__(cls)
        return cls.instance



def task(name):
    ob = Singleton(name)
    print(ob)


for i in range(5):
    t = threading.Thread(target=task,args=[i,])
    t.start()


**Ai.小助手:** 在给定的修改后的代码中,将 `time.sleep(1)` 移动到 `__new__` 方法的条件判断之后。这样做会导致在多线程环境下数据不安全。

在原始代码中,`time.sleep(1)` 在 `__init__` 方法中,表示在每次实例化时都会休眠1秒。这样,即使多个线程同时尝试实例化 `Singleton` 类,由于每个线程都会在 `__init__` 方法中休眠1秒,确保了每个线程都能够安全地完成实例化。

但是,在修改后的代码中,将 `time.sleep(1)` 移动到 `__new__` 方法的条件判断之后。这样做会导致以下问题:

1. 多个线程同时进入 `__new__` 方法,并发现 `cls.instance` 为 `None`。
2. 第一个线程开始休眠1秒。
3. 在第一个线程休眠期间,其他线程继续执行,并进入 `__new__` 方法。
4. 其他线程发现 `cls.instance` 仍然为 `None`,于是也开始休眠1秒。
5. 第一个线程完成休眠后,创建了一个实例并将其赋值给 `cls.instance`。
6. 其他线程完成休眠后,也创建了一个实例,但由于 `cls.instance` 已经被第一个线程赋值,它们不会再次赋值。

因此,由于多个线程都能够创建实例并赋值给 `cls.instance`,所以数据不再安全。每个线程将拥有自己的实例,而不是共享同一个实例。

为了确保数据的安全性,应该将 `time.sleep(1)` 保留在 `__init__` 方法中,以确保每个线程都能够按顺序休眠并实例化对象。

**王寄鱼:** 第一个单例和以下单例有什么区别?为什么以下单例线程不安全?

class Singleton(object):

    def __init__(self):
        import time
        time.sleep(1)
    
    @classmethod
    def instance(cls, *args, **kwargs):
        if not hasattr(Singleton, "_instance"):
            Singleton._instance = Singleton(*args, **kwargs)
    
        return Singleton._instance

import threading

def task(arg):
    obj = Singleton.instance()
    print(obj)

for i in range(5):
    t = threading.Thread(target=task,args=[i,])
    t.start()

**Ai.小助手:** 第一个单例实现和第二个单例实现之间有以下区别:

1. 在第一个单例实现中,使用了 `__new__` 方法来控制对象的创建过程,而在第二个单例实现中,使用了一个类方法 `instance` 来获取单例对象。
2. 第一个单例实现中,使用了一个类变量 `instance` 来保存单例对象,而第二个单例实现中,使用了一个类属性 `_instance` 来保存单例对象。

以下单例实现是线程不安全的,原因如下:

1. 多个线程同时进入 `instance` 方法,并发现 `Singleton._instance` 为 `None`。
2. 第一个线程开始执行 `Singleton(*args, **kwargs)` 创建实例,并将其赋值给 `Singleton._instance`。
3. 在第一个线程执行期间,其他线程继续执行,并进入 `instance` 方法。
4. 其他线程发现 `Singleton._instance` 仍然为 `None`,于是也开始执行 `Singleton(*args, **kwargs)` 创建实例。
5. 其他线程完成创建实例后,将实例赋值给 `Singleton._instance`,覆盖了第一个线程创建的实例。

因此,多个线程可以同时创建实例并赋值给 `Singleton._instance`,导致每个线程拥有自己的实例,而不是共享同一个实例。这就导致了线程不安全的问题。

为了确保线程安全,可以使用线程锁(例如 `threading.Lock`)来保护关键部分的代码,确保只有一个线程可以同时访问和操作 `Singleton._instance`。这样可以避免多个线程同时创建实例的问题。

**王寄鱼:** 把time.sleep(1)注释后,为什么又线程安全了?

class Singleton(object):

    def __init__(self):
        import time
        # time.sleep(1)
    
    @classmethod
    def instance(cls, *args, **kwargs):
        if not hasattr(Singleton, "_instance"):
            Singleton._instance = Singleton(*args, **kwargs)
    
        return Singleton._instance

import threading

def task(arg):
    obj = Singleton.instance()
    print(obj)

for i in range(5):
    t = threading.Thread(target=task,args=[i,])
    t.start()

**Ai.小助手:** 在给定的代码中,将 `time.sleep(1)` 注释掉后,代码在多线程环境下仍然是线程不安全的。

虽然注释掉了 `time.sleep(1)`,消除了每个线程之间的休眠时间差异,但问题仍然存在。具体原因如下:

1. 多个线程同时进入 `instance` 方法,并发现 `Singleton._instance` 为 `None`。
2. 第一个线程开始执行 `Singleton(*args, **kwargs)` 创建实例,并将其赋值给 `Singleton._instance`。
3. 在第一个线程执行期间,其他线程继续执行,并进入 `instance` 方法。
4. 其他线程发现 `Singleton._instance` 仍然为 `None`,于是也开始执行 `Singleton(*args, **kwargs)` 创建实例。
5. 其他线程完成创建实例后,将实例赋值给 `Singleton._instance`,覆盖了第一个线程创建的实例。

因此,多个线程可以同时创建实例并赋值给 `Singleton._instance`,导致每个线程拥有自己的实例,而不是共享同一个实例。这就导致了线程不安全的问题。

为了确保线程安全,同样可以使用线程锁(例如 `threading.Lock`)来保护关键部分的代码,确保只有一个线程可以同时访问和操作 `Singleton._instance`。这样可以避免多个线程同时创建实例的问题。

**王寄鱼:** 以下两个单例,为什么一个线程安全,另一个不安全?
例子1:
class Singleton(object):

    def __init__(self):
        import time
        time.sleep(1)
    
    @classmethod
    def instance(cls, *args, **kwargs):
        if not hasattr(Singleton, "_instance"):
            Singleton._instance = Singleton(*args, **kwargs)
    
        return Singleton._instance


例子2:
class Singleton(object):
    instance = None

    def __init__(self, name):
        self.name = name
        time.sleep(1)


    # @synchronized
    def __new__(cls, *args, **kwargs):
        # 关键在于这,每一次实例化的时候,我们都只会返回这同一个instance对象
        if not cls.instance:
            cls.instance = super(Singleton, cls).__new__(cls)
        return cls.instance


**Ai.小助手:** 在例子1中,虽然存在多线程,但代码仍然是线程不安全的。原因是在 `instance` 方法中,多个线程可以同时检查 `hasattr(Singleton, "_instance")` 为 `False`,然后同时创建实例并赋值给 `Singleton._instance`。

具体过程如下:

1. 多个线程同时进入 `instance` 方法,并发现 `hasattr(Singleton, "_instance")` 为 `False`。
2. 第一个线程开始执行 `Singleton(*args, **kwargs)` 创建实例,并将其赋值给 `Singleton._instance`。
3. 在第一个线程执行期间,其他线程继续执行,并进入 `instance` 方法。
4. 其他线程同样发现 `hasattr(Singleton, "_instance")` 为 `False`,于是也开始执行 `Singleton(*args, **kwargs)` 创建实例。
5. 其他线程完成创建实例后,将实例赋值给 `Singleton._instance`,覆盖了第一个线程创建的实例。

因此,多个线程可以同时创建实例并赋值给 `Singleton._instance`,导致每个线程拥有自己的实例,而不是共享同一个实例。这就导致了线程不安全的问题。

在例子2中,虽然也存在多线程,但代码是线程安全的。原因是在 `__new__` 方法中使用了互斥锁(`super().__new__(cls)`)来保护关键部分的代码,确保只有一个线程可以同时访问和操作 `cls.instance`。

具体过程如下:

1. 多个线程同时进入 `__new__` 方法,并发现 `cls.instance` 为 `None`。
2. 第一个线程获得互斥锁,开始执行 `super().__new__(cls)` 创建实例,并将其赋值给 `cls.instance`。
3. 在第一个线程执行期间,其他线程无法获得互斥锁,因此无法执行 `super().__new__(cls)`,只能等待。
4. 第一个线程完成创建实例后,释放互斥锁。
5. 其他线程竞争获得互斥锁,其中一个线程获得锁后发现 `cls.instance` 已经被赋值,直接返回该实例。
6. 其他线程无法获得锁的线程会继续等待,直到锁被释放。

由于使用了互斥锁,确保了同一时间只有一个线程可以执行关键部分的代码,从而避免了多个线程同时创建实例的问题,保证了线程安全性。



三、简单单例模式实现

单例模式,是一个设计思路,既然是一种设计思路,那么肯定就有很多种实现方法。

首先介绍一下单例模式的思路,我们的目的是确保某一个类只有一个实例存在,减少内存的损耗。比如我们要读取一个配置文件的时候,可能很多地方都需要读取这个文件,那么可能会创建出十几个对象都在读这个文件,并且他们的功能都是相同的。那么我们不需要创建那么多对象了,每次都把相同的对象返回给他。

单例模式常用写法:懒汉式(懒汉模式在多线程下有数据安全问题,需要使用线程锁),饿汉式,注册式,序列化式。

本文介绍四种实现单例模式的方法。

1、纯单例模式的实现

记住单例的实现思路,无论看什么方法实现单例都是一样的。首先在类中定义一个属性如x为空,然后再调用类的时候就可以判断了,如果是第一次进来,x必定为空,那么就正常的创建一个对象给他。那么我们类中的x属性现在就不是空了,而是一个对象,因为我们把创建出来的对象赋值给了x,那么之后每次进来,因为x已经不是空了,所以都不会走创建对象的分支,而是走直接return x的分支,我们又没有对x进行修改,他永远都是第一次创建出来的对象。

1、利用__new__魔术方法

在调用的时候若没有这个对象再临时创建这个对象,属于懒汉式

class MyClass:
    __obj = None

    def __new__(cls, *args, **kwargs):
        if cls.__obj:
            return cls.__obj
        cls.__obj = object.__new__(cls)
        return cls.__obj


obj1 = MyClass()
obj2 = MyClass()
print(obj1)
print(obj2)
---------------------------
<__main__.MyClass object at 0x7f9e22a45460>
<__main__.MyClass object at 0x7f9e22a45460>

2、直接利用类方法

在调用的时候若没有这个对象再临时创建这个对象,属于懒汉式

class MyClass:
    __obj = None

    @classmethod
    def singleton(cls):
        if cls.__obj:
            return cls.__obj
        cls.__obj = cls()
        return cls.__obj


obj1 = MyClass.singleton()
obj2 = MyClass.singleton()
print(obj1)
print(obj2)
------------------------
<__main__.MyClass object at 0x7fb321340460>
<__main__.MyClass object at 0x7fb321340460>

3、利用元类

思路是在调用前就先把这个对象创建出来,然后要调用的时候直接返回这个对象。属于饿汉式

class MyMeta(type):
    __obj = None

    def __init__(self,class_name,class_bases,class_dic):
        self.__obj = object.__new__(self)  # 先把对象创建出来

    def __call__(self, *args, **kwargs):
        return self.__obj  # 不用判断是否有这个对象了,直接创建即可

class MyClass(metaclass=MyMeta):
    pass

obj1 = MyClass()
obj2 = MyClass()
print(obj1)
print(obj2)
------------------------
<__main__.MyClass object at 0x7ffbc4a28c70>
<__main__.MyClass object at 0x7ffbc4a28c70>

4、利用函数装饰器

思路是在调用前就先把这个对象创建出来,然后要调用的时候直接返回这个对象。属于饿汉式

def wrapper(func):
    __obj = func()
    def inner(*args,**kwargs):
        return __obj
    return inner

@wrapper
class MyClass():
    pass

obj1 = MyClass()
obj2 = MyClass()
print(obj1)
print(obj2)
--------------------
<__main__.MyClass object at 0x7fefc632db80>
<__main__.MyClass object at 0x7fefc632db80>




# 这个单例的分析思路:
博主曾经被这个单例困惑了几个月,直到现在和朋友讨论才发现了这个单例的实现流程,博主的问题是:
我们用装饰器的时候,装饰器里面的代码肯定是会走的,而我们执行了两次MyClass(),也就是__obj = func(),那么在wrapper这个
装饰器里,func()这个代码应该是走两次的,那么生成的对象肯定应该是两个不一样的,但是结果却是实现了单例。
在解决这个困惑的过程中,加入了一些代码:
def wrapper(func):
    __obj = func()
    print(1)
    def inner(*args,**kwargs):
        print(2)
        return __obj
    return inner

@wrapper
class MyClass():
    pass

上述的代码,执行结果是,1走了一遍,2走了两遍。当然,只有这样的结果才是符合单例的,但是却和我们的思路不一样。
然后我把@wrapper拆分了出来
代码就变成了
def wrapper(func):
    __obj = func()
    def inner(*args,**kwargs):
        return __obj
    return inner


class MyClass():
    pass


MyClass = wrapper(MyClass)
obj1 = MyClass()
obj2 = MyClass()
print(obj1)
print(obj2)
然后我瞬间懂了,那个print(1),是在MyClass = wrapper(MyClass)走的,然后,这个MyClass其实变成了inner
后面其实就是直接执行inner,根本就和wrapper没关系了。

5、利用python导包实现(最常用)

# s1.py 文件中

class Foo(object):
    def test(self):
        print("123")

v = Foo()
# v是Foo的实例
------


# s2.py 文件中

from s1 import v as v1
print(v1,id(v1))  #<s1.Foo object at 0x0000000002221710>  35788560

from s1 import v as v2
print(v1,id(v2))   #<s1.Foo object at 0x0000000002221710>  35788560

# 两个的内存地址是一样的
# 文件加载的时候,第一次导入后,再次导入时不会再重新加载。

6、利用类装饰器

def Singleton(cls):
    """
    使用一个字典,实现能装饰多个不同的类实现单例
    :param cls: 
    :return: 
    """
    _instance = {}

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

    return _singleton


@Singleton
class Test(object):
    t = 1

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


@Singleton
class Test1(object):
    t = 1

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



t1 = Test(2)
t2 = Test(3)
t3 = Test1(4)
t4 = Test1(5)

2、单例模式的实际使用

以下也是一种设计的方案,当使用者实例化时传了参数,那么说明他想创建一个新的对象,如果不传参,我们默认使用单例模式。下面提供了三种方式。

1、利用类方法

IP = 1.1.1.1
PORT = 3306

class MySQL:
    __instance = None

    def __init__(self, ip, port):
        self.ip = ip
        self.port = port

    @classmethod
    def singleton(cls):
        if cls.__instance:
            return cls.__instance
        cls.__instance = cls(IP, PORT)
        return cls.__instance


obj1=MySQL("1.1.1.1",3306)
obj2=MySQL("1.1.1.2",3306)
obj3 = MySQL.singleton()
obj4 = MySQL.singleton()
print(obj1)
print(obj2)
print(obj3)
print(obj4)

2、利用装饰器

IP = "192.168.11.10"
PORT = 3306

import settings

def outter(func):  # func = MySQl类的内存地址
    _instance = func(IP,PORT)
    def wrapper(*args,**kwargs):
        if args or kwargs:
            res=func(*args,**kwargs)
            return res
        else:
            return _instance
    return wrapper

@outter  # MySQL=outter(MySQl类的内存地址)  # MySQL=》wrapper
class MySQL:
    def __init__(self, ip, port):
        self.ip = ip
        self.port = port


obj1 = MySQL("1.1.1.1", 3306)
obj2 = MySQL("1.1.1.2", 3306)
obj3 = MySQL()
obj4 = MySQL()
print(obj1)
print(obj2)
print(obj3 is obj4)

3、利用元类

IP = 1.1.1.1
PORT = 3306

class Mymeta(type):
    __instance = None
    def __init__(self,class_name,class_bases,class_dic):
        self.__instance=object.__new__(self)  # Mysql类的对象
        self.__init__(self.__instance,IP,PORT)

    def __call__(self, *args, **kwargs):
        if args or kwargs:
            obj = object.__new__(self)
            self.__init__(obj, *args, **kwargs)
            return obj
        else:
            return self.__instance

          
class MySQL(metaclass=Mymeta):
    def __init__(self, ip, port):
        self.ip = ip
        self.port = port


obj1 = MySQL("1.1.1.1", 3306)
obj2 = MySQL("1.1.1.2", 3306)
obj3 = MySQL()
obj4 = MySQL()
print(obj1)
print(obj2)
print(obj3 is obj4)

'''
关于为何要在Mymeta中书写__call__方法,那是因为我们现在要控制Mymeta实现单例模式,那么如果不自己实现__call__就会使用type的__call__方法,type是不可能帮我们实现单例模式的。
'''
posted @ 2020-11-30 20:40  王寄鱼  阅读(120)  评论(0编辑  收藏  举报