zoukankan      html  css  js  c++  java
  • 元类

    元类

    一切源自一句话:python中一切皆对象.

    class StanfordTeacher(object):
        school='Stanford'
    
        def __init__(self,name,age):
            self.name=name
            self.age=age
    
        def say(self):
            print('%s says welcome to the Stanford to learn Python' %self.name)
    

    所有对象都是实例化或者说调用类而得到的(调用类的过程为类的实例化),比如对象t1是调用类StanfordTeacher得到的

    t1=StanfordTeacher('lili',18)
    print(type(t1)) #查看对象t1的类是<class '__main__.StanfordTeacher'>
    

    如果一切皆为对象,那么类StanfordTeacher本质也是一个对象,既然所有的对象都是调用类得到的,那么StanfordTeacher必然也是调用了一个类得到的,这个类称为元类

    于是我们可以推导出====>产生StanfordTeacher的过程一定发生了:StanfordTeacher=元类(...)

    print(type(StanfordTeacher)) # 结果为<class 'type'>,证明是调用了type这个元类而产生的StanfordTeacher,即默认的元类为type
    

    class关键字创建类的流程分析

    上文我们基于python中一切皆为对象的概念分析出:我们用class关键字定义的类本身也是一个对象,负责产生该对象的类称之为元类(元类可以简称为类的类),内置的元类为Type

    class关键字在帮我们创建类是,必然帮我们调用了元类StanfordTeacher = type(...),那调用type时传入的参数是什么?

    必然是类的关键组成部分,一个类有三大组成部分,分别是

    1,类名class_name = 'StanfordTeacher '

    2,基类们class_bases = (object)

    3,类的名称空间class_bases = (object)

    4,调用type时会依次传入以上三个参数

    StanfordTeacher = type(class_name,class_bases,class_dic)

    # 1、先拿到一个类名
    class_name = "StanfordTeacher"
    
    # 2、然后拿到类的父类
    class_bases = (object,)
    
    # 3、再运行类体代码,将产生的名字放到名称空间中
    class_dic = {}
    class_body = """
    school = 'oldboy'
    
    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)
    """
    exec(class_body,{},class_dic)
    # print(class_dic)
    
    # 4、调用元类(传入类的三大要素:类名、基类、类的名称空间)得到一个元类的对象,然后将元类的对象赋值给变量名StanfordTeacher ,StanfordTeacher 就是我们用class自定义的那个类
    StanfordTeacher  = type(class_name,class_bases,class_dic)
    

    exec的用法

    #exec:三个参数
    
    #参数一:包含一系列python代码的字符串
    
    #参数二:全局作用域(字典形式),如果不指定,默认为globals()
    
    #参数三:局部作用域(字典形式),如果不指定,默认为locals()
    
    #可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中
    g={
        'x':1,
        'y':2
    }
    l={}
    
    exec('''
    global x,z
    x=100
    z=200
    
    m=300
    ''',g,l)
    
    print(g) #{'x': 100, 'y': 2,'z':200,......}
    print(l) #{'m': 300}
    

    自定义元类控制类的OldboyTeacher的创建

    一个类没有声明自己的元类,默认他的元类就是Type,除了使用内置元类Type,我们可以通过继承type来自定义元类,然后使用metaclass关键字参数为一个类指定元类

    class Mymeta(type):  # 只有继承了type类的类才是自定义的元类
        pass
    
    
    class OldboyTeacher(object, metaclass=Mymeta):
        school = 'oldboy'
    
        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)
    
    
    # 1、先拿到一个类名:"OldboyTeacher"
    # 2、然后拿到类的父类:(object,)
    # 3、再运行类体代码,将产生的名字放到名称空间中{...}
    # 4、调用元类(传入类的三大要素:类名、基类、类的名称空间)得到一个元类的对象,然后将元类的对象赋值给变量名OldboyTeacher,oldboyTeacher就是我们用class自定义的那个类
    OldboyTeacher = Mymeta("OldboyTeacher",(object,),{...})
    

    自定义元类可以控制类的产生过程,类的产生过程其实就是元类的调用过程

    调用Mymeta会先产生一个空对象,然后连同调用Mymeta括号内的参数一同传给Mymeta下的__init__方法,完成初始化.

    import re
    
    
    class Mymeta(type):  # 只有继承了type类的类才是自定义的元类
        def __init__(self, class_name, class_bases, class_dic):
            # print(self)  # 类<class '__main__.OldboyTeacher'>
            # print(class_name)
            # print(class_bases)
            # print(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("必须要有文件注释,并且注释内容不为空")
    
    # OldboyTeacher = Mymeta("OldboyTeacher",(object,),{...})
    class OldboyTeacher(object,metaclass=Mymeta):
        """
        adsaf
        """
    
        school = 'oldboy'
    
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def say(self):
            print('%s says welcome
    

    自定义元类控制类的OldboyTeacher的调用

    储备知识:call

    import re
    
    
    class Mymeta(type):  # 只有继承了type类的类才是自定义的元类
        def __init__(self, class_name, class_bases, class_dic):
            # print(self)  # 类<class '__main__.OldboyTeacher'>
            # print(class_name)
            # print(class_bases)
            # print(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("必须要有文件注释,并且注释内容不为空")
    
        # res = OldboyTeacher('egon',18)
        def __call__(self, *args, **kwargs):
            # 1、先创建一个老师的空对象
            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
    
    
    # OldboyTeacher = Mymeta("OldboyTeacher",(object,),{...})
    class OldboyTeacher(object, metaclass=Mymeta):
        """
        adsaf
        """
    
        school = 'oldboy'
    
        #            tea_obj,'egon',18
        def __init__(self, name, age):
            self.name = name  # tea_obj.name='egon'
            self.age = age  # tea_obj.age=18
    
        def say(self):
            print('%s says welcome to the oldboy to learn Python' % self.name)
    
    
    res = OldboyTeacher('egon', 18)
    print(res.__dict__)
    # print(res.name)
    # print(res.age)
    # print(res.say)
    
    # 调用OldboyTeacher类做的事情:
    # 1、先创建一个老师的空对象
    # 2、调用老师类内的__init__方法,然后将老师的空对象连同括号内的参数的参数一同传给__init__
    # 3、将初始化好的老师对象赋值给变量名res
    

    单例模式

    单例模式是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在.无论调用多少次产生的实例对象,都是指向同一个内存地址,仅仅只有一个实例(只有一个对象)

    1,使用模块

    python的模块就是单例模式,因为模块在第一次导入时,会产生.pyc文件,当第二次导入时,就会直接加载.pyc文件,而不会再次执行模块代码,因此,我们只需把相关的函数和数据定义在一个模块中,就可获得一个单例对象

    2,通过元类
    import settings
    
    
    class Mymeta(type):
        __instance = None
        def __init__(self,class_name,class_bases,class_dic):
            self.__instance=object.__new__(self)  # Mysql类的对象
            self.__init__(self.__instance,settings.IP,settings.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
    
    # MySQL=Mymeta(...)
    class MySQL(metaclass=Mymeta):
        def __init__(self, ip, port):
            self.ip = ip
            self.port = port
            
            
    obj3 = MySQL()
    obj4 = MySQL()
    
    print(obj3 is obj4)
    # 原理:类定义时会调用元类下的__init__,类调用(实例化对象)时会触发元类下的__call__方法
    # 类定义时,给类新增一个空的数据属性,
    # 第一次实例化时,实例化之后就将这个对象赋值给类的数据属性;第二次再实例化时,直接返回类的这个数据属性
    
    
    通过绑定方法classmethod
    import settings
    
    
    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(settings.IP, settings.PORT)
            return cls.__instance
    
    
    
    obj3 = MySQL.singleton()
    print(obj3)
    
    obj4 = MySQL.singleton()
    print(obj4)
    
    
    通过装饰器
    import settings
    
    def outter(func):  # func = MySQl类的内存地址
        _instance = func(settings.IP,settings.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)
    # print(obj1)
    # print(obj2)
    
    obj3 = MySQL()
    obj4 = MySQL()
    print(obj3 is obj4)
    

    属性查找

    class Mymeta(type):
        n=444
    
        # def __call__(self, *args, **kwargs): #self=<class '__main__.OldboyTeacher'>
        #     obj=self.__new__(self)
        #     print(self.__new__ is object.__new__) #True
    
    
    class Bar(object):
        # n=333
    
        # def __new__(cls, *args, **kwargs):
        #     print('Bar.__new__')
        pass
    
    class Foo(Bar):
        # n=222
    
        # def __new__(cls, *args, **kwargs):
        #     print('Foo.__new__')
        pass
    
    class OldboyTeacher(Foo,metaclass=Mymeta):
        # n=111
    
        school='oldboy'
    
        def __init__(self,name,age):
            # self.n=0
            self.name=name
            self.age=age
    
        def say(self):
            print('%s says welcome to the oldboy to learn Python' %self.name)
    
    
        # def __new__(cls, *args, **kwargs):
        #     print('OldboyTeacher.__new__')
    
    
    # obj=OldboyTeacher('egon',18)
    # print(obj.n)
    
    print(OldboyTeacher.n)
    

    总结:

    Myneta下的__call__里的self.new__在OldboyTeacher,Foo,Bar里都没有找到__new__的情况下,会去找object里的__new,而object下默认就有一个__new__,所以即便是之前的类均未实现__new__,也一定会在object中找到一个,根本不会,也根本没必要再去找元类Mymeta=>type中查找__new__

  • 相关阅读:
    Android 系统开发做什么?
    MySQL索引-B+树
    转:redis雪崩、穿透、击穿
    转:django3上线部署踩得坑
    nginx、uwsgi部署django中session丢失得问题
    类型转换(数字转字符串等)
    JS基础篇1:数据类型(8种)
    css3动画与js动画的区别
    drag拖拽事件
    三栏布局,中间自适应
  • 原文地址:https://www.cnblogs.com/lgh8023/p/13524515.html
Copyright © 2011-2022 走看看