美文网首页
Python中类创建的过程以及元类的理解

Python中类创建的过程以及元类的理解

作者: 铜锣湾洪爷 | 来源:发表于2019-01-10 17:27 被阅读0次

    首先类是一个什么东西

    类是数据与操作其数据方法的封装

    一个定义python类的例子:

    class B:
        def rename(self, newname):
            self.name = newname
    class A(B):   # 继承B,能用B的公有属性和方法
        name = 'A'     # 公有属性
        def __init__(self):
            self.name = 'a'  # 私有属性
        def get_name(self):  # 方法(公有)
             return self.name
    a = A()
    print(a.get_name())   # a
    a.rename('b')
    print(a.get_name())   # b
    

    于此,能解决python编程中90%以上的应用
    python中类的创建与其实例化如此简单,背后却是因为底层做好了足够的封装,那么究竟在python里面类是种什么样的东西呢?

    一步步来...

    在python中:

    处处都是对象

    因此我们上面实例出的a是一个对象,定义的类A、B也是对象,相信看到此处很多初学者都是一脸懵逼的,那么它怎么是一个对象呢?

    先来介绍python中的对象, 避免复杂化问题,这里简化出对象实际上是占用内存中的一个东西:

    内存中对象.png

    python的解析器是用c写的,那么,在c中描述这个对象的话,用的是一个c结构体,而这个结构体里面,不止上面对象那么简单,而是:


    复杂一点点的对象.png

    在这个对象(结构体)里面,有一个fn的东西叫做引用计数,这个暂时放一放,先来讨论class这个变量,这个变量说明(指向)了一个xx(暂时未知)的类型,在python设计的''游戏规则''里面,此时就能够告诉我们,这一个对象是一个xx类型的对象,既然在python里面处处都是对象,那么这个对象指向类型,也应该是一个对象,如图:


    对象的类型.png

    类型(对象2)也是一个对象,但是对象2的类型是谁呢?总不可能这样这样嵌套下去吧,对的,python的设计开始要把这个玩法''圆''起来了,那么是什么呢?

    type

    对象2的类型就是type,'自圆其说'的终点,这个type是python底层封装好的一个类型(结构体),按照设计的一致,type对象里面也有类型指向,那么怎么终止嵌套呢?type的类型指向了它自己.....嵌套结束....那么这个'游戏'的规则是这样的:


    类型链.png

    一个小验证:

    class A:
        pass
    a = A()
    print(a.__class__)         # <class '__main__.A'>
    print(A.__class__)         # <class 'type'>
    print(type.__class__)     # <class 'type'>
    

    也就是说a的类型是A,A的类型是type,type的类型还是type

    感觉越跑越偏了,python中的类创建和元类跟这些'游戏规则'有什么联系?现在也没涉及到'创建'这个概念啊,那是因为还缺少一个东西,让'游戏'拥有'创建'的功能...

    object

    缺少的东西就是这个----object,我们知道,在python2.x的时候看见一些代码在写类的时候必须继承一个object,如下:

    class human(object):
        def run(self):
            print 'i am running...'
    

    就连type中也要....

    class type(object):
        ...
    

    那么这个object很有可能就是拥有创建一切能力的东西,类都要继承于它(暂时看来),当然,python底层也封装好了这个object类对象,那么它应该也有对应指向的类型啊,它的类型是什么呢?

    print(object.__class__)
    # <class 'type'>
    

    ......绕晕了,object的类型竟然是type,也就是说type要继承object,而object的类型是type,object不继承任何东西(继承的顶端)

    果不其然,用起来简单的东西,里面的设计就是这么复杂且绕.....

    先介绍类'玩法'的两个概念性的东西:
    实例化:由类实例化出来对象的一个功能
    继承: 子类继承于父类,也就是子类可以用父类的公有属性和方法

    type和object就是为了满足这个游戏规则而设计出来的产物,都是为了结束嵌套而早就封装定义好的两个结构体(对象)
    type: 我是实例化的顶端
    object: 我是继承的顶端

    纵观这个python设计里面,加上object之后就是这样的了:


    关系.png

    从图中看,现在的游戏规则就是:
    所有'类型链'的顶端是type 。所有'继承链'的顶端是object

    类型是能够'提供'给使用者创建出实例的功能,图中的type和对象2都需要有这样的一个功能,在图中看出他们都继承自object,那么根据继承的玩法,如果object中有这样的一个'创建出'实例的功能,那就皆大欢喜了。

    没错,object就是有这样的功能------在内存中建立'对象'

    也就是图中的黑色框那玩意,是object._new_()出来的,被object._new_()出来之后也只是一个空壳,并没有我们想要定义的东西啊例如自定义属性、方法之类的,当然,在object创建出的空框之后,就会调用我们定义类的_new_方法,而我们常用的_init_方法则是之后才调用,保证让你想怎么样就怎么样

    实例一个类的过程

    class A(object):
        # 我们自定义这个类的__new__方法
        def __new__(cls, *args, **kwargs):
            print('创建的时候用父类object的__new__方法获得一个实例(空)')
            instance = super().__new__(cls, *args, **kwargs)
            print('在此自定义实例化后增加的东西')
            return instance
        def __init__(self, *args, **kwargs):
            print('最后用到自定义的__init__')
            pass
    a = A()
    得出:
    # 创建的时候用父类object的__new__方法获得一个实例(空)
    # 在此自定义实例化后增加的东西
    # 最后用到自定义的__init__
    

    实例化调用链:
    my_class._new_() ------> 父类(object)._new_() ------> instance(实例)

    此时解决了我们正常使用的时候定义的class xxx实例化的过程,但是,我们定义的类也是一个对象啊,也需要它的类型进行实例化这样的一个过程,上面讲到,类对象的类型是type,人家封装好了,我们还怎么进去分析甚至是用它? 想要它的能力的话,那就继承它吧....也是接下来说的----元类...

    元类metaclass

    概念不难懂,就是上述定义的类型A的类型, 简称类的类(很绕),也就是元类...,要使用元类的话必须要有type的'功能',那我们就继承它吧....

    class my_meta(type):
        pass
    

    咋一看又是一个class......看到此处也是懵逼的,那么先把疑问抛弃,看一下这玩意能够实例出什么来,运用上面类进行实例的过程!

    调用my_meta._new_()方法的时候,找父类(继承于type),父类是type,就是要调用type._new_(),type也有父类啊(继承于object),又调用object._new_()去创建一个实例空壳,回来之后这个实例空壳回到了type的_new_方法中,由于type被设计者封装了,里面就是把这个实例'赋予'类属性和方法,也就是最上面图中对象2里面的属性跟方法。

    class my_meta(type):
        # 这个实例过程需要传入3个参数
        # 元类,类名, 继承, 属性方法
        def __new__(mcls,  name, bases, attrs):
            # 调用type的.__new__方法,其中调用object生成空壳
            # 对该空壳根据传入type中的参数mcls, name, bases, attrs进行填补进去 
            # cls = super().__new__(mcls, name, bases, attrs)
            cls = type.__new__(mcls, name, bases, attrs)
            return cls
    my_class = my_meta('my_class', (), {'name':'my_class'})
    print(my_class.name)
    # my_class拥有实例功能(证明我是一个类)
    m = my_class()
    print(m.name)
    # my_class
    # my_class
    

    我们由自定义my_meta类继承于type,实例出来的就像我们平时定义的类,也就是说,我们现在可以动态控制类的生成。
    更通用的写法:

    class my_meta(type):
        # 这个实例过程需要传入3个参数
        # 元类,类名, 继承, 属性方法
        def __new__(mcls,  name, bases, attrs):
            # 调用type的.__new__方法,其中调用object生成空壳
            # 对该空壳根据传入type中的参数mcls, name, bases, attrs进行填补进去 
            # cls = super().__new__(mcls, name, bases, attrs)
            print('改你想改的(传入参数修改)!')
            cls = type.__new__(mcls, name, bases, attrs)
            print('改你想改的(返回之后的修改)!')
            return cls
    
    # 显式传入metaclass参数,否则仍然把这个类对象用type实例出来
    class A(metaclass=my_meta):
        # 程序这里的名字A,该类继承于xx,类方法__new__和__init__和speak
        # 将会传入my_meta中作为参数(name, bases, attrs)
        def __new__(cls, *args, **kwargs):
            return super().__new__(cls, *args, **kwargs)
        def __init__(self, *args, **kwargs):
            pass
        def speak(self):
            print('speaking..')
    
    # 改你想改的(传入参数修改)!
    # 改你想改的(返回之后的修改)!
    

    最后的输出就说明了我们写好了这个类时候,会经过my_meta的创建过程!

    解决一些坑

    1、有人会问,我们不是还写了class my_meta吗,那它这是谁创建的啊?
    根据游戏规则,my_meta的类是谁?查找到父类type,它的类是type自身,new出这个my_meta就是调用了type的new再往上object的new,而type是底层封装好的,终止嵌套。。。

    2、实例化的时候,调用的是类加括号进行实例化(A()),是怎么回事?
    当调用A()的时候,python会从A的类中查找其_call_方法,当A的类为type的时候,也就是调用type._call_(A, *args, **kwargs),里面调用A._new_(A, *args, **kwargs),同上进行实例化过程...

    也就是为什么我们在定义一个类的时候,定义了其_call_方法就可以是其类的实例拥有'可调用'的功能:

    class A:
        def __init__(self):
            self.name = 'a'
        def __call__(self, sth):
            return self.name + ' speak ' + sth 
    
    a = A()
    print(a('hello'))
    
    # a speak hello
    

    这就是同理在type中控制了其实例出来的东西拥有'可调用'的功能,并且调用之后是实例化该类,故此我们实例一个类的时候直接A()即可,伪代码:

    class type:
        def __new__(...):
            ...
        def __call__(cls, *args, **kwargs):
            # 调用其__new__方法
            instance = cls.__new__(cls, *args, **kwargs)
            # 调用其__init__方法进行初始化
            instance.__init__(*args, **kwargs)
            return instance
    

    3、type和object这样的鸡生蛋蛋生鸡的关系,究竟为什么要这么绕?
    type和object都是为了python这样的对象系统而存在的,至于为什么要这么设计,只能说,游戏规则由设计者定...javascript甚至其他语言的对象系统都有自己的一套方法,type和object正是为了给这个游戏提供支持而已。

    最后

    这里只是显浅地对python中类创建过程和元类的理解,并不完全严谨,要理解其语言的强大之处还需要懂c语言并且阅读其解析器的源代码

    相关文章

      网友评论

          本文标题:Python中类创建的过程以及元类的理解

          本文链接:https://www.haomeiwen.com/subject/axgorqtx.html