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

2019-01-10  本文已影响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语言并且阅读其解析器的源代码

上一篇下一篇

猜你喜欢

热点阅读