41、Python之面向对象:一切皆对象,元类与类对象的创建

引言

在前面,我们介绍了Python中一切皆为对象的理念,并在系列文章中反复提及这个理念。感兴趣的同学可以阅读:Python番外篇:万法归一,一切皆对象,这篇文章。

紧接着,我们通过了对Python的标准类型层次结构的梳理,印证了Python中包括类、函数等都是对象,不记得的可以回看:Python番外篇:标准类型层次结构,这篇文章。

当时,我们在这篇文章中,简单提及了几个有点奇怪的结论:
1、Python中所有的类对象,都是通过type类实例化的;
2、Python中所有的类的最顶层的父类都是object;
3、object这个类对象是由type类实例化的;
4、type这个类对象是由type类自己实例化的。

当时只是一带而过,也许不少刚接触Python的同学,会有些困惑。由于当时还没有涉及面向对象的部分,就没有展开。

相信通过之前面向对象内容的介绍,以及本文关于元类(metaclass)概念的补充,将对这些结论有一些更加深入的理解。

需要提前说明的是,由于篇幅的限制,本文主要进一步阐释Python中一切皆对象的概念,通过元类的概念,向上不断溯源,探究实例对象通过类来创建,那么类对象又是如何被创建的。

所以,本文所介绍的更多是原理类的“无用”之学,关于元类的实际应用场景的“有用”之学,将在下一篇文章中进行展开介绍。

“元”的概念与“元类”

虽然“元”的概念稍显晦涩,但是,稍微类比一下是不难理解的。

比如,思考,有的人的思考方式,是遇到一个问题,仔细分析所有已知条件和约束,然后通过逻辑推理,一步步缜密地推导出问题的解;有的人则是“羚羊挂角,无迹可寻”,一会儿东,一会儿西地大胆假设,最终也能得出问题的解。

相对于“思考”的对象是问题,所谓的“元思考”思考的对象是思考本身, 是反观内省是如何进行思考本身的。所以,元思考,就是思考的思考。

比如,相较于“学习”的对象是知识、技能,“元学习”的对象是学习本身,元学习是学习如何学习的含义,是学习的学习。

那么,相同的逻辑,我们来理解“类”与“元类”的概念。

“类”实例化的对象是类模板所创造出来的实例化对象,“元类”作用的对象不再是实例化对象,而是类对象,是创建类的类。

一切皆对象,类也是对象,被称为类对象(一定要明确区分类对象与实例对象)。所以,元类就是创建类对象的类,元类就是类的类,这里的“类”,如同学习、思考,是动词。

这个概念有点绕,笔者的表达能力又不太靠谱,所以,稍微理解一下,实在不理解,自行搜索引擎吧。

再看type的用法

之前的文章中已经提到过了,type是一个类,而不是内置函数,我们来再次看一下type类的定义:

c69a761a2250daf5f08c2e05e2515996.jpeg

其实,从type的定义描述中,可以看到,我们可以有两种方式获取一个类:

1、type(object):通过一个现有的对象,获取这个对象所属的类。

2、type(name, bases, dict, **kwargs):基于各个参数凭空创建一个新的类。

接下来,我们通过代码演示一下type的使用:

# 通过class进行类的定义
class DaGongRen:
    def __init__(self, name):
        self.name = name


if __name__ == '__main__':
    zs = DaGongRen('张三')
    print(zs.name)
    print(type(zs))
    # type方式1:基于对象获取类然后创建对象
    ls = type(zs)('李四')
    print(ls.name)
    print(type(ls))
    print(isinstance(ls, DaGongRen))

    # type方式2:通过type元类进行类对象的构建
    # 用于通过type进行显示类的定义,作为实例初始化方法的实现
    def my_init(obj, name):
        obj.name = name


    DaGongRen2 = type('DaGongRen2', (), {'__init__': my_init})
    # 调用类的__init__方法,本质上是调用my_init
    ww = DaGongRen2('王五')
    print(ww.name)
    print(type(ww))

执行结果:

021447ba4366307978f0fef0b0dabc90.jpeg

通过代码及执行结果,我们可以有如下结论:

1、基于一个现有的实例对象,通过type可以获取该实例的所属类对象,进而通过()调用的方式创建一个新的对象。所以,从结果的层面看,我们基于一个实例对象,创建出了一个同类的实例对象。

2、创建类对象的方式有两种,一种是显式地通过class关键字进行类的定义,另一种就是通过type()基于各种参数凭空进行一个类对象的创建。

type是一切元类的基类

前面我们通过type的第二种用法创建了一个全新的类对象,后续都可以基于这个类对象进行实例对象的创建。所以,可以看出type是在创建类,是类的类,所以type是一个元类。

其实,在Python中有一个关键字metaclass,这个在collections.abc中曾经看到过:

e2635050288c39a61f5b29f14fd472e8.jpeg

collections.abc中最基础的几个抽象基类:Sized、Container的定义中,均通过metaclass关键字指明了用于构建类对象的元类为ABCMeta。

而从ABCMeta的定义中可以看出,ABCMeta继承自type。

3d0e5356fd12c9de7f3383793d824aa6.jpeg

之所以说type是一切元类的基类,是因为:

1、我们在定义类时,不指定创建类对象的元类时,默认的元类都是type。

2、当我们想要自定义一个元类时,通常需要继承自type或者其子类。

关于第一点,我们可以通过type(类名)来看到,以实际代码来看:

# 通过class进行类的定义
class DaGongRen:
    def __init__(self, name):
        self.name = name


class DaGongRen2(metaclass=type):
    def __init__(self, name):
        self.name = name


if __name__ == '__main__':
    print(type(DaGongRen))
    zs = DaGongRen2('张三')
    print(type(zs))
    print(type(DaGongRen2))

执行结果:

73484d9ebec33f4293f1a8f5211bad6d.jpeg

我们可以尝试定义一个元类,直接看代码:

class DaGongRenMeta(type):
    def __new__(cls, name, bases, namespace, **kwargs):
        print(f"{cls}这个类对象是基于DaGongRenMeta这个元类创建的")
        return super().__new__(cls, name, bases, namespace, **kwargs)


print("创建类对象之前")


class DaGongRen(metaclass=DaGongRenMeta):
    def __init__(self, name):
        self.name = name


print("创建类对象之后")

if __name__ == '__main__':
    zs = DaGongRen('张三')
    print(zs.name)
    print(type(zs))
    print(type(DaGongRen))
    print(type(DaGongRenMeta))

执行结果:

098f9f22af96fabc23e3086760359eb2.jpeg

从代码及执行结果,可以看出:

1、自定义元类的方法是:自定义一个类继承自type,并实现__new__方法,一般实现的__new__方法中,要调用父类(也就是type)的__new__方法,用于进行类对象的创建。

2、类对象的创建,是在类定义时发生的,即便没有使用该类创建实例对象,也会首先进行类对象的创建。类定义时会自动调用所属元类的__new__方法。

3、指定元类进行类的定义时,type(类名)就不再返回type了,而是对应的元类。

4、自定义的元类本身与type具有二重关系,其一,继承自type,所以该元类的父类(基类)是type;其二,该元类的类对象仍然是由type进行创建的。

总结

本文仍然以“一切皆对象”的概念为出发点及主轴,进行了相关面向对象的知识的补充说明。首先简单介绍了“元”的概念,并通过类比的方式引出来“元类”的概念。其次,回顾了type的定义,并通过代码实例展示了type的两种用法。最后,通过代码的演示,说明了type是一切元类的类,是定义类的默认元类的概念。

需要说明的是,今天的内容更偏向于底层原理的介绍,感兴趣的可以自行进行进一步的深入研究。关于元类的具体使用场景,将在下一篇文章中进行介绍。

感谢您的拨冗阅读,如果对您学习Python有所帮助,欢迎点赞、关注。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南宫理的日知录

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值