百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

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

bigegpt 2024-12-29 01:33 3 浏览

引言

在前面,我们介绍了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 = 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))

执行结果:

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

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 = 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))

执行结果:


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

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))

执行结果:

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

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

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

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

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


总结

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

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


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



相关推荐

为3D手游打造, Visual Studio Unity扩展下载

IT之家(www.ithome.com):为3D手游打造,VisualStudioUnity扩展下载7月30日消息,微软正式发布升级版VisualStudioToolsforUnity扩...

由ArcMap属性字段自增引出字段计算器使用Python的技巧

1.前言前些日子有人问我ArcMap中要让某个字段的值实现自增有什么方法?我首先想到像SQLServer中对于数值型字段可以设置自增。所以我打开ArcCatalog查看发现只提供默认值,没办法只能看...

微软首次回答 HoloLens 相关问题,终于爆料了

fengo2015/04/2115:11注:本文作者张静是NVIDIAGPU架构师,微信公众号“黑客与画家”(HackerAndPainter),知乎专栏地址。欢迎各位童鞋与他交流探讨。...

C#指针的应用(c#指针类型)

C#在有限的范围内支持指针。C#的指针只不过是一个持有另一类型内存地址的变量。但是在C#中,指针只能被声明为持有值类型和数组的内存地址。与引用类型不同,指针类型不被默认的垃圾收集机制所跟踪。出于同...

C# 堆栈(Stack)(c# 堆栈中定位调用messagebox 的地方)

C#集合在C#中,堆栈(Stack)是一种后进先出(LIFO,LastInFirstOut)的数据结构。堆栈(Stack)适用于存储和按顺序处理数据,其中最新添加的元素会最先被移除。堆...

欢迎回来:Fortran意外重回流行编程语言20强榜单

TIOBE指数是用来确定一种编程语言受欢迎程度的指标之一。它并不表明哪种编程语言是最好的,也不表明哪种编程语言写的代码行数最多,而是利用在谷歌、维基百科、必应、亚马逊、YouTube等各种引擎和网站上...

C#+NET MAUI实现跨平台/终端(linux,win,ios等)解决方案

简介.NETMulti-platformAppUI(.NETMAUI)是一个跨平台的框架,用于使用C#和XAML创建移动和桌面应用程序。使用.NETMAUI,您可以用一套代码库开发可以在A...

C#代码安全红线:SQL注入防护终极方案,让你的系统固若金汤

在数字化时代,应用系统的安全性至关重要。而SQL注入攻击,长期盘踞在OWASP(OpenWebApplicationSecurityProject)漏洞榜单的前列,成为众多基于数据库的应用系统...

C# (一)状态机模式(状态机代码实现)

最近空闲,炒炒隔夜饭,以前这些模式在自己项目种应用过不少,但一直没有像别人那样写一个系列,最近年纪大了,很多东西都忘记了,特别AI的兴起,更少写代码了,反正没什么事情,自己在重写一遍吧。创建型模式(5...

C# 中 Predicate 详解(c#中的replace)

Predicate泛型委托:表示定义一组条件并确定指定对象是否符合这些条件的方法。此委托由Array和List类的几种方法使用,用于在集合中搜索元素。Predicate<T>...

C#中$的用法?(c#中&&什么意思)

文章来自AI问答。在C#中,$符号用于字符串插值(StringInterpolation)。字符串插值是C#6.0引入的一种特性,它允许你在字符串中直接嵌入表达式,而不需要使用string.For...

C#并行编程:Parallel类(c# 并行处理)

在Parallel类中提供了三个静态方法作为结构化并行的基本形式:Parallel.Invoke方法:并行执行一组委托。Parallel.For方法:执行与C#for循环等价的并行方法。Parall...

颠覆认知!用Span重构foreach循环竟让数据处理快如闪电

在C#编程的世界里,数据处理效率始终是开发者们关注的焦点。随着项目规模的扩大和数据量的激增,哪怕是细微的性能提升,都可能对整个应用的响应速度和用户体验产生深远影响。近年来,C#引入的Span<T...

Unity3D手游开发实践《腾讯桌球》客户端开发经验总结

本次分享总结,起源于腾讯桌球项目,但是不仅仅限于项目本身。虽然基于Unity3D,很多东西同样适用于Cocos。本文从以下10大点进行阐述:1.架构设计2.原生插件/平台交互3.版本与补丁4.用脚本,...

.NET 7 AOT 的使用以及 .NET 与 Go 互相调用

目录背景C#部分环境要求创建一个控制台项目体验AOT编译C#调用库函数减少体积C#导出函数C#调用C#生成的AOTGolang部分安装GCCGolang导出函数.NETC#...