zoukankan      html  css  js  c++  java
  • Python之元类

    1、引子(类也是对象)

    exec:三个参数

    参数一:字符串形式的命令

    参数二:全局作用域(字典形式),如果不指定,默认就是用全局 globals()

    参数三:局部作用域(字典形式),如果不指定,默认就是用局部 locals()

    exec会在指定的局部作用域内执行字符串内的代码,除非明确地使用global关键字

    g={
        'x':1,
        'y':2,
        'teachers':{'egon','alex','yuanhao','wupeiqi'}
    }
    l={'birds':[1,2,3]}
    
    # exec("""
    # def func():
    #     global x
    #     x='egon'
    # func()
    # """,g,l)
    # print(g)
    # print(l)
    
    exec("""   # exec 当成一个函数的执行
    global x
    x='egon'
    """,g,l)   # 需要指定一下全局作用域是谁和局部作用域是谁
    print(g)
    print(l)
    
    知识储备exec命令
    class Foo:
        x=1
        def run(self):
            pass
    # print(type(Foo))
    f1=Foo()  # f1 是通过 Foo 类实例化的对象

    python中一切皆是对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象(这里的对象指的是类而非类的实例),因而我们可以将类当作一个对象去使用,同样满足第一类对象的概念,可以:

    把类赋值给一个变量

    把类作为函数参数进行传递

    把类作为函数的返回值

    在运行时动态地创建类

    上例可以看出f1是由Foo这个类产生的对象,而Foo本身也是对象,那它又是由哪个类产生的呢?

    2、什么是元类?

    元类是类的类,是类的模板

    元类是用来控制如何创建类的,正如类是创建对象的模板一样,而元类的主要目的是为了控制类的创建行为

    元类的实例化的结果为我们用class定义的类,正如类的实例为对象(f1对象是Foo类的一个实例,Foo类是 type 类的一个实例)

    type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象

    3、创建类的两种方式

    方式一:使用class关键字

    class Chinese(object):
        country='China'
        def __init__(self,name,age):
            self.name=name
            self.age=age
        def talk(self):
            print('%s is talking' %self.name)

    方式二(就是手动模拟class创建类的过程):将创建类的步骤拆分开,手动去创建

    准备工作:

    创建类主要分为三部分

      1 类名

      2 类的父类

      3 类体

    步骤一(先处理类体->名称空间):类体定义的名字都会存放于类的名称空间中(一个局部的名称空间),我们可以事先定义一个空字典,生成类的局部名称空间

    class_name='Chinese'  #模拟类名
    class_bases=(object,)  # 模拟新式类继承 object 类
     
    country='China'  # 模拟类的数据属性
    
    def __init__(self,name,age):  # 模拟类的 init 方法
        self.name=name
        self.age=age
    
    def talk(self):   # 模拟类的函数属性
        print('%s is talking' %self.name)
    
    class_dic={     # 模拟类的命长空间 __dict__
        'country':country,
        '__init__':__init__,
        'talk':talk,
    }

    步骤二:调用元类type(也可以自定义)来产生类Chinense

    Chinese=type(class_name,class_bases,class_dic) 
    #实例化 type 得到对象Chinese,即我们用class定义的类Chinese
    
    
    print(Chinese)
    print(type(Chinese))
    print(isinstance(Chinese,type))
    '''
    <class '__main__.Chinese'>
    <class 'type'>
    True
    '''
    
    我们看到,type 接收三个参数:
    
        第 1 个参数是字符串 ‘Chinese’,表示类名
    
        第 2 个参数是元组 (object, ),表示所有的父类
    
        第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法
    
    补充:若Chinese类有继承,即class Chinese(Bar):.... 则等同于type('Chinese',(Bar,),{})

    4、元类总结

    一个类没有声明自己的元类,默认他的元类就是type,除了使用元类type,用户也可以通过继承type来自定义元类(顺便我们也可以瞅一瞅元类如何控制类的行为,工作流程是什么)

    class Mymeta(type):
        def __init__(self,name,bases,dic):
            print('===>Mymeta.__init__')
        # print(self)  #  <class '__main__.Foo'>
             # print(class_name)  # Foo
             # print(class_bases)  # () 
             # print(class_dic) # Foo 的属性
    
    
    
        def __new__(cls, *args, **kwargs):
            print('===>Mymeta.__new__')
            return type.__new__(cls,*args,**kwargs)
    
        def __call__(self, *args, **kwargs):
            print('aaa')
            obj=self.__new__(self)
            self.__init__(self,*args,**kwargs)
        # self 是 Foo,跑的对象自己的 init,obj.name='egon',obj 是 f  
            return obj  # 返回 f  
    
    
    class Foo(metaclass=Mymeta):
        def __init__(self,name):
            self.name=name
        def __new__(cls, *args, **kwargs):
            return object.__new__(cls)
    
    
    ''' 需要记住一点:名字加括号的本质(即,任何name()的形式),都是先找到name的爹,然后执行:爹.__call__ 而爹.__call__一般做两件事: 主要用来控制实例化的行为 1.调用name.__new__方法,并返回一个对象 2.进而调用name.__init__方法对儿子name进行初始化 '''
    '''
    class 定义Foo,并指定元类为Mymeta,这就相当于要用Mymeta创建一个新的对象Foo,于是相当于执行
    Foo=Mymeta('foo',(...),{...})
    因此我们可以看到,只定义class就会有如下执行效果
    ===>Mymeta.__new__
    ===>Mymeta.__init__
    实际上class Foo(metaclass=Mymeta)是触发了Foo=Mymeta('Foo',(...),{...})操作,
    遇到了名字加括号的形式,即Mymeta(...),于是就去找Mymeta的爹type,然后执行type.__call__(...)方法
    于是触发Mymeta.__new__方法得到一个具体的对象,然后触发Mymeta.__init__方法对对象进行初始化
    '''
    
    '''
    obj=Foo('egon')  # Foo 在元类里面是一个对象,对象(),就执行 call 方法,Foo.__call__(Foo,'egon')   
    Foo()  # 对象本身不能加括号运行,可以运行的原因是因为继承的元类里面有 call 方法
    '''
    
    
    ''' 总结:元类的难点在于执行顺序很绕,其实我们只需要记住两点就可以了 1.谁后面跟括号,就从谁的爹中找__call__方法执行 type->Mymeta->Foo->obj Mymeta()触发type.__call__ Foo()触发Mymeta.__call__ obj()触发Foo.__call__ 2.__call__内按先后顺序依次调用儿子的__new__和__init__方法 '''

    单例模式:优化内存,无论你实例化多少次,始终用同一个对象

    # 方式1
    class Mysql:
        __instance=None  # 先定义一个空
    
        def __init__(self):
            self.host='127.0.0.1'
            self.port=3306
    
        @classmethod  # 绑定类的方法,由类来调用
        def single(cls): # 实例化对象的时候要通过这个类的这个方法实例化
            if cls.__instance is None:   # 实例化的时候看下是否为空
                obj=cls()  # 正常实例化
                cls.__instance=obj  # 实例化实例化后赋值
                return cls.__instance  # 返回对象
    
        def conn(self):
            pass
    
        def execute(self):
            pass
    
    
    obj1=Mysql.single()
    obj2=Mysql.single()
    print(obj1 is obj2) # true 两个实例都是同一个内存空间
    #方式2:元类的方式
    class Mymeta(type):
        def __init__(self,class_name,class_bases,class_dic):
            if not class_name.istitle():
                raise TypeError('类名的首字母必须大写')
    
            if '__doc__' not in class_dic or not class_dic['__doc__'].strip():
                raise TypeError('必须有注释,且注释不能为空')
    
            super(Mymeta,self).__init__(class_name,class_bases,class_dic)
            self.__instance=None
    
        def __call__(self, *args, **kwargs): #obj=Chinese('egon',age=18)
            if not self.__instance:  # 初始 __instance 的属性不存在,self 是 mysql 类
                obj=object.__new__(self)  # 手动调用 objcect.__new__方法生成一个空对象
                self.__init__(obj)  # 手动调用 Mysql.__init__() 的初始化方法,init 里面需要传入 self 对象,self 是 obj
                self.__instance=obj  # 属性赋值 obj
    
            return self.__instance  # 返回属性也就返回实例化对象
    
    
    
    class Mysql(object,metaclass=Mymeta):
        '''
        mysql xxx
        '''
        def __init__(self):
            self.host='127.0.0.1'
            self.port=3306
    
        def conn(self):
            pass
    
        def execute(self):
            pass
    
    
    
    obj1=Mysql()
    obj2=Mysql()
    obj3=Mysql()
    
    print(obj1 is obj2 is obj3)

    5、练习题

    class Mymeta(type):
         def __init__(self,class_name,class_bases,class_dic):
             # pass
             # print(self)
             # print(class_name)
             # print(class_bases)
             # print(class_dic)
             for key in class_dic:  # 遍历子类下面的每个属性
                if not callable(class_dic[key]):continue
                if not class_dic[key].__doc__ or not class_dic['__doc__'].strip():  #  如果属性里面没有注释,就下面报错
                    raise TypeError('小子,你没写注释,赶紧去写')
    
             # type.__init__(self,class_name,class_bases,class_dic)
    
             # if hasattr(self,'__doc__'):  # 类有没有 doc
             #    if not self.__dict__['__doc__']:  # 类的 doc 有没有注释,没有就报错
             #        raise  TypeError('没有注释')
    
    class Foo(metaclass=Mymeta):
        x=1
        def run(self):
            'run function'  # __doc__
            print('running')
    
        # def __doc__(self):
        #     print('hahahahaha')
    
    # Foo=Mymeta('Foo',(object,),{'x':1,'run':run})
    
    print(Foo.__dict__)
    print(Foo.__dict__['run'].__doc__)  # 打印函数属性的 doc
    1、限制类内的属性必须有文档注释
    class Mymetaclass(type):
        def __new__(cls, name,bases,attrs):
            update_attrs={}
            for k,v in attrs.items():
                print(type(k))  # 字典里面的键是字符串
                if not callable(k) and not k.startswith('__'):
                    # 如果值为不可调用的,且数据属性不是以 __ 开头,就把数据属性名变为大写
                    update_attrs[k.upper()]=v
                else:
                    update_attrs[k]=v
                
            return type.__new__(cls,name,bases,update_attrs)
    
    class Foo(metaclass=Mymetaclass):
        country='China'
        tag='Dragon'
        def walk(self):
            print('%s is walking'%Foo.tag)
    
    
    print(Foo.__dict__)
    # print(callable(Foo.walk))
    a='abc'
    def abc():
        pass
    if not callable('abc') and not a.startswith('__'):
        print('ok')
    else:
        print('no')
    2、在元类中控制把自定义类的数据属性都变成大写
    class Mymetaclass(type):
        # def __new__(cls,name,bases,attrs):
        #     update_attrs={}
        #     for k,v in attrs.items():
        #         if not callable(v) and not k.startswith('__'):
        #             update_attrs[k.upper()]=v
        #         else:
        #             update_attrs[k]=v
        #     return type.__new__(cls,name,bases,update_attrs)
    
        def __call__(self, *args, **kwargs):
            print(args)
            if args: # 如果只传了一个值,就报错
                raise TypeError('must use keyword argument for key function')
            obj = object.__new__(self) #创建对象,self为类 Chinese
    
            for k,v in kwargs.items():
                obj.__dict__[k.upper()]=v
            return obj
    
    class Chinese(metaclass=Mymetaclass):
        country='China'
        tag='Legend of the Dragon' #龙的传人
        def walk(self):
            print('%s is walking' %self.name)
    
    
    p=Chinese(name='EGON')
    print(p.__dict__)
    ----------------
    ()
    {'NAME': 'EGON'}
    3、在元类中控制自定义的类无需__init__方法

    .

  • 相关阅读:
    linux kernel内存碎片防治技术
    内核线程
    Linux内核高端内存
    Lcd(一)显示原理
    LSB和MSB
    图解slub
    数据库小试题2
    编写函数获取上月的最后一天
    php中的static静态变量
    mysql小试题
  • 原文地址:https://www.cnblogs.com/tootooman/p/9225626.html
Copyright © 2011-2022 走看看