龙空技术网

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

南宫理的日知录 270

前言:

当前看官们对“python包含关系”大致比较关心,你们都需要学习一些“python包含关系”的相关内容。那么小编同时在网络上收集了一些关于“python包含关系””的相关资讯,希望小伙伴们能喜欢,看官们快快来了解一下吧!

引言

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

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

当时,我们在这篇文章中,简单提及了几个有点奇怪的结论:

1、Python中所有的类对象,都是通过type类实例化的;

2、Python中所有的类的最顶层的父类都是object;

3、object这个类对象是由type类实例化的;

4、type这个类对象是由type类自己实例化的。

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

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

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

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

“元”的概念与“元类”

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

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

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

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

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

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

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

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

再看type的用法

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

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

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

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

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

# 通过class进行类的定义class DaGongRen:    def __init__(self, name):        self.name = nameif __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))

执行结果:

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

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

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

type是一切元类的基类

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

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

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

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

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

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

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

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

# 通过class进行类的定义class DaGongRen:    def __init__(self, name):        self.name = nameclass DaGongRen2(metaclass=type):    def __init__(self, name):        self.name = nameif __name__ == '__main__':    print(type(DaGongRen))    zs = DaGongRen2('张三')    print(type(zs))    print(type(DaGongRen2))

执行结果:

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

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 = nameprint("创建类对象之后")if __name__ == '__main__':    zs = DaGongRen('张三')    print(zs.name)    print(type(zs))    print(type(DaGongRen))    print(type(DaGongRenMeta))

执行结果:

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

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

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

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

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

总结

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

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

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

标签: #python包含关系