zoukankan      html  css  js  c++  java
  • 元类与单例模式

    一、元类

    元类是什么

    有一句话在编程语言圈中流行,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('egon', 18)
    print(res.__dict__)
    ----------------------------------
    {'_Teacher__name': 'egon', '_Teacher__age': 18}
    

    二、单例模式***

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

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

    单例模式常用写法:懒汉式,饿汉式,注册式,序列化式。

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

    纯单例模式

    利用__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>
    

    直接利用类方法

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

    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>
    

    利用元类

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

    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>
    

    利用装饰器

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

    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没关系了。
    

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

    按照需求实现单例模式

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

    利用类方法

    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)
    
    

    利用装饰器

    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)
    

    利用元类

    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是不可能帮我们实现单例模式的。
    '''
    
  • 相关阅读:
    Eclipse护眼技巧
    Maven搭建SSM框架(Spring+SpringMVC+MyBatis)
    Spring之各jar包作用
    Maven新建web项目jsp报错
    js金额转大写(万元为单位)
    linux常用指令
    ie8下数组不支持indexOf方法解决方法
    string,stringBuffer,stringBuilder的比较
    input限制输入
    spring boot Mybatis --maven
  • 原文地址:https://www.cnblogs.com/chiyun/p/14063581.html
Copyright © 2011-2022 走看看