zoukankan      html  css  js  c++  java
  • python元类深入解析

    元类

    什么是元类

    元类是类的类,是类的模板(就如对象的模板是类一样)

    元类的实例为类,类的实例为对象

    元类是用来产生类的

    动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,是运行时动态创建的

    __new__()

    我们之前说类实例化第一个调用的是__init__,但__init__其实不是实例化一个类的时候第一个被调用 的方法。当使用 Persion(name, age) 这样的表达式来实例化一个类时,最先被调用的方法 其实是 __new__ 方法。

    __new__方法接受的参数虽然也是和__init__一样,但__init__是在类实例创建之后调用,而 __new__方法正是创建这个类实例的方法。

    class A:
        pass
    
    
    class B(A):
        def __new__(cls):
            print("__new__方法被执行")
    
        def __init__(self):
            print("__init__方法被执行")
    
    
    b = B()
    

    __new__方法被执行

    __call__()

    当类中有__call__方法时,实例对象直接加括号,会执行这个方法,你拿到的实例对象就是它返回的

    class zx():
        def __call__(self,name,age):
            print(name)
            print(age)
    
    zx125=zx()
    zx125("wl",18)
    

    wl
    18

    内置函数type()

    type()函数既可以返回一个对象的类型,又可以创建出新的类型

    创建一个新的class时需要传入3个参数:

    1.class的名称

    2.继承父类的集合(元组)

    3.函数名和函数对象的键值对(字典)

    通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。

    def speank(self):
        print("llllllllllll")
    
    zx=type("zx",(object,),dict(say=speank))
    zx1=zx()
    zx1.say()
    print(type(zx1))
    print(type(zx))
    

    llllllllllll
    <class 'main.zx'>
    <class 'type'>

    内置函数exec()

    执行字符串的代码,当成python解释器,把代码丢给解释器解释

    exec(s,g,l)

    g表示当前的一个全局名称空间,当前的一个l局部名称空间,代码解析完毕,会按属性是全局和局部,放入以上参数
    例1

    cmd = """
    x=1
    print('exec函数运行了')
    def func(self):
        pass
    """
    class_dic = {}
    # 执行cmd中的代码,然后把产生的名字丢入class_dic字典中
    exec(cmd, {}, class_dic)
    
    print(class_dic)
    

    {'x': 1, 'func': <function func at 0x10a0bc048>}

    例2

    x = 10
    expr = """
    z = 30
    sum = x + y + z
    print(sum)
    """
    
    y = 20
    m={'x':1,'y': 2}
    w={'y': 3, 'z': 4}
    exec(expr)
    exec(expr,m)
    exec(expr,m,w)
    print(w)
    

    60
    33
    34
    {'y': 3, 'z': 30, 'sum': 34}

    元类

    class关键字

    例1 理解class关键字

    可见这时候已经生成了一个zx类对象了,zx的属性字典已经被写入了,那么这个过程中到底发生了什么呢?接下来我们慢慢拆解

    class Mytype(type):
        pass
    
    class zx(metaclass=Mytype):#代码运行到这相当于 zx=Mytype("zx",(object,),{}),此时已经生成了zx类对象了
        pass
    
    print(zx.__dict__)
    
    {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'zx' objects>, '__weakref__': <attribute '__weakref__' of 'zx' objects>, '__doc__': None}
    

    例2

    创建一个对象,说到底就是用了,上面介绍的方法__init(),__new__(),__call()这三个方法,真正创建对象的只有type()这个内置方法。下面直接进入主题,直接重写元类的三个方法。

    1.首先,会运行到class Mytype(type)同理可得,这个时候会生成一个Mytype对象,这是一个关键,自定义的元类创建好了,下面生产类对象就靠它了,这个元类的产生过程是修改不了的,因为是c实现的

    2.然后就和例1一样zx=Mytype("zx",(object,),{}),接下来就是三个方法的运用了。首先是Mytype后面加的括号,所以去执行type__call__()方法

    3.然后看一下__call__()方法的参数self, *args, **kwargs,有个self,这个self就是第一步创建的那个Mytype对象,然后会依次执行self.__new__()self.__init__()方法,也就是Mytype中的这两个方法,这两个方法的作用,主要就是创建对象,给对象填入相关的属性,而__call__()主要就是对操作的封装,然后返回组装完成的类对象

    4.我们看到结果,只打印了new,但是按上面的步骤,不是还应该打印一个init的吗?别急,在看打印生成的对象zx它竟然是个None,所以其中必要蹊跷,发生了什么呢?其实过程还是和上面一样的,只不过我们重写了方法,导致运行到__new__()的时候,这个方法,返回了一个None,这怎么行呢?所以当返回值返回到__call__()的时候,导致直接出了异常,方法中断了,但是没有打印异常,是因为源码做了处理,返回了一个None

    class Mytype(type):
        def __init__(self,*args, **kwargs):
            print("init")
        def __call__(self, *args, **kwargs):
            print("call")
        def __new__(cls, *args, **kwargs):
            print("new")
    
    class zx(metaclass=Mytype):#代码运行到这相当于 zx=Mytype("zx",(object,),{}),此时已经生成了zx类对象了
        pass
    
    print(zx)
    
    new
    None
    

    例3 实现创建类对象

    出现了这样的问题当然要解决他呢!首先要注意类对象是由自定义元类生成的,实例对象是由类对象生成的,这两种情况的方法执行流程是相同的,但是有些细节的偏差

    1.我把打印结果移到了,__init__()方法去了,但是发现执行这个方法的时候,我们想要的zx类对象其实已经创建好了,这就是主要的区别,生成类对象,和属性填充等操作都会放在__new__()里面,而创建实例对象的时候,一些添加属性的过程,则会放到__init__()里面

    2.在__new__()方法中,我创建对象用的方法是type.__new__(cls,*args, **kwargs),为啥呢?因为我们自定义元类的时候,如果没有重写__new__()方法是时候,他就找不到这个方法,这个时候当然就是去父类找这个方法,那Mytype的父类是谁呢?就是type,其实原理是一样的

    3.我们成功自定义了元类,并且拿到了zx类对象,我们可以发现整个过程我们都是可以控制的,自定义元类给我们极大的操作过程,那么接下来我们来操作一下实例对象生成的过程

    class Mytype(type):
        def __init__(self,*args, **kwargs):
            print(self)
            print(self.__dict__)
            print("init")
        def __call__(self, *args, **kwargs):
            print("call")
        def __new__(cls, *args, **kwargs):
            # print(cls)
            # return object.__new__(cls)
            return self.__new__(cls,*args, **kwargs)
    
    class zx(metaclass=Mytype):#代码运行到这相当于 zx=Mytype("zx",(object,),{}),此时已经生成了zx类对象了
        pass
    
    
    <class '__main__.zx'>
    {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'zx' objects>, '__weakref__': <attribute '__weakref__' of 'zx' objects>, '__doc__': None}
    init
    

    例4 实例对象的创建过程

    这里我把Mytype__init__()__new__()方法去掉,让他直接调用默认的方法就行,这里不研究类对象的生成了

    1.首先来查看打印结果,一个为空,这就是上面所说的区别,在创建实例对象的时候,使用的是不是type__new__方法,而是,找zx继承类,一般都是创建一个空对象,除非你自定义一个父类,从写它的__new__()方法

    2.创建完了一个空对象,然后调用__init__()方法给他进行填值,最后我们拿到了自己想要的实例对象了

    class Mytype(type):
        def __call__(self, *args, **kwargs):
            obj=self.__new__(self, *args, **kwargs)
            print(obj.__dict__)
            obj.__init__( *args, **kwargs)
            return obj
    
    class zx(metaclass=Mytype):#代码运行到这相当于 zx=Mytype("zx",(object,),{}),此时已经生成了zx类对象了
        def __init__(self,name,age):
            self.name=name
            self.age=age
    
        def run(self):
            print("runrun")
    
    ha=zx("小明",18)
    print(ha.__dict__)
    
    {}
    {'name': '小明', 'age': 18}
    

    总结

    1.一切皆对象

    2.注意自定义元类的特殊性,他是父类是type,在创建类对象的时候他的__new__()方法,直接把创建对象和添加属性字典的操作都完成了

    3.注意对象+()调用的是,元类!的__call()方法,默认元类都是type

    4.太难了,太绕了,记一下,容易忘

    __new__()一共接收4个参数(也可以使用位置形参接收)

    底层就是调用了type()方法

    分别为:

    1.当前准备创建的类对象;

    2.类的名字

    3.类继承的父类集合

    4.类的方法集合

  • 相关阅读:
    2.分布式锁
    1. junit用法,before,beforeClass,test,after, afterClass的执行顺序
    GC算法
    记一次"截图"功能的前期调研过程!
    程序员转行手册!
    Yarn详细的工作流程
    Yarn的三种调度器(Scheduler)
    Hadoop序列化与Java序列化的区别
    MapReduce执行过程
    从普通登录到单点登录图例
  • 原文地址:https://www.cnblogs.com/zx125/p/11460454.html
Copyright © 2011-2022 走看看