• 元类,exec()用法,自定义元类控制子类的创建 —call—用法,属性查找顺序,单例模式


    一花一世界,一叶一菩提

    元类

    1.什么是元类?:

    一切源自于一句话: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必然也是调用了一个类得到的,这个类称为元类

    #  调用OldboyTeacher类=》对象obj
    #  调用元类=》OldboyTeacher类
    
    #我们查看一下这个OldboyTeacher的类名叫什么
    print(type(OldboyTeacher))
    

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

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

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

    class关键字在帮我们创建类时,必然帮我们调用了元类StanfordTeacher=type(…),那调用type时传入的参数是什么呢?必然是类的关键组成部分,一个类有三大组成部分,分别是

    1、类名class_name='StanfordTeacher’

    2、基类们class_bases=(object,)

    3、类的名称空间class_dic,类的名称空间是执行类体代码而得到的

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

    综上,class关键字帮我们创建一个类应该细分为以下四个过程

    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}
    

    3.自定义元类控制类StanfordTeacher的创建

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

    class Mymeta(type):  # 只有继承了type类的类才是自定义的元类
        pass
    class OldboyTeacher(object, metaclass=Mymeta):#用metaclass去指定元类
        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)
    

    自定义元类可以控制类的产生过程,类的产生过程其实就是元类的调用过程,即StanfordTeacher=Mymeta(‘StanfordTeacher’,(object),{…}),调用Mymeta会先产生一个空对象StanfordTeacher,然后连同调用Mymeta括号内的参数一同传给Mymeta下的init方法,完成初始化,于是我们可以

    class Mymeta(type):  # 只有继承了type类的类才是自定义的元类
        def __init__(self, class_name, class_bases, class_dic):
            # print(self)  # 类<class '__main__.OldboyTeacher'>
            # print(class_name)
            # print(class_bases)  #(<class 'object'>,)
            # 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 to the oldboy to learn Python' % self.name)
    

    4. ——call——

    class Foo:
        def __call__(self, *args, **kwargs):
            print(self)
            print(args)
            print(kwargs)
    obj=Foo()
    #1、要想让obj这个对象变成一个可调用的对象,需要在该对象的类中定义一个方法__call__方法,该方法会在调用对象时自动触发
    #2、调用obj的返回值就是__call__方法的返回值
    res=obj(1,2,3,x=1,y=2)
    

    由上例得知,调用一个对象,就是触发对象所在类中的call方法的执行,如果把StanfordTeacher也当做一个对象,那么在StanfordTeacher这个对象的类中也必然存在一个call方法

    默认地,调用res=StanfordTeacher(‘egon’,18)会做三件事

    1、产生一个空对象obj

    2、调用init方法初始化对象obj

    3、返回初始化好的obj

    对应着,StanfordTeacher类中的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("必须要有文件注释,并且注释内容不为空")
        # res = OldboyTeacher('egon',18)
        def __call__(self, *args, **kwargs):
            # 1、先创建一个老师的空对象
            tea_obj = object.__new__(self)
            # 2、调用老师类内的__init__函数,然后将老师的空对象连同括号内的参数的参数一同传给__init__
            self.__init__(tea_obj, *args, **kwargs)
            # 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.name)
    print(res.age)
    print(res.say)
    

    结果:

    egon
    18
    <bound method OldboyTeacher.say of <__main__.OldboyTeacher object at 0x000001FBDC4810A0>>
    

    5.属性查找:

    • 结合python继承的实现原理+元类重新看属性的查找应该是什么样子呢???

    • 在学习完元类后,其实我们用class自定义的类也全都是对象(包括object类本身也是元类type的 一个实例,可以用type(object)查看),我们学习过继承的实现原理,如果把类当成对象去看,将下述继承应该说成是:对象StanfordTeacher继承对象Foo,对象Foo继承对象Bar,对象Bar继承对象object

    先上图看看:

    img

    上代码:(请仔细想清楚。)

    class Mymeta(type): #只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类
        n=444
        def __call__(self, *args, **kwargs): #self=<class '__main__.StanfordTeacher'>
            obj=self.__new__(self)
            self.__init__(obj,*args,**kwargs)
            return obj
    class Bar(object):
        n=333
    class Foo(Bar):
        n=222
    class StanfordTeacher(Foo,metaclass=Mymeta):
        n=111
        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)
    
    print(StanfordTeacher.n)
    #自下而上依次注释各个类中的n=xxx,然后重新运行程序,发现n的查找顺序为StanfordTeacher->Foo->Bar->object->Mymeta->type
    

    查找顺序:

    #查找顺序:
    #1、先对象层:StanfordTeacher->Foo->Bar->object
    #2、然后元类层:Mymeta->type
    

    单例模式

    什么单例模式:

    • 单例:即单个实例,指的是同一个类实例化多次的结果指向同一个对象,用于节省内存空间

    下面我们介绍一下单例模式的三种方法:

    # 六:单例模式
    # 实现方式1: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
    
    obj1=MySQL("1.1.1.1",3306)
    obj2=MySQL("1.1.1.2",3306)
    print(obj1)
    print(obj2)
    
    obj3 = MySQL.singleton() #调用singleton
    print(obj3)
    
    obj4 = MySQL.singleton()
    print(obj4)
    
    
    
    # 方式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
    
    
    obj1 = MySQL("1.1.1.1", 3306)
    obj2 = MySQL("1.1.1.2", 3306)
    print(obj1)
    print(obj2)
    
    obj3 = MySQL()
    obj4 = MySQL()
    print(obj3)
    print(obj4)
    print(obj3 is obj4)
    
    
    
    # 方式3:装饰器
    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:  #如果不满足我们就默认他是需要进行单利。直接把最上面代码运行所生成的_instance 返回出来。
                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)
    
    

    作业:

    1.在元类中控制以驼峰体形式命名类名:

    2.在元类中控制必须继承一个父类:

    3.在元类中控制必须要有文件注释,且注释内容不可为空:

    4.数据私有化

    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("至少继承一个父类")
            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__)
    
    努力学习!
  • 相关阅读:
    关于${pageContext.request.contextPath}的理解
    Spring中的八大设计模式
    mybatis pagehelper分页插件使用
    什么是JavaConfig
    springboot优缺点
    Redis持久化的两种方式和配置
    未能加载文件或程序集“System.Web.Http.WebHost, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35”或它的某一个依赖项。系统找不到指定的文件。
    关于AJAX跨域调用ASP.NET MVC或者WebAPI服务的问题及解决方案
    C#关于微信昵称中存在的表情图标乱码解决
    移动端调用相机拍照上传图片
  • 原文地址:https://www.cnblogs.com/Orange-YXH/p/13648097.html
Copyright © 2020-2023  润新知