当前位置: 首页 > 后端技术 > Python

【Python源码分析】对象模型概述

时间:2023-03-25 22:44:51 Python

Python是一种面向对象的语言,实现了完整的面向对象体系,简洁优雅。与其他面向对象的编程语言相比,Python有其独特的一面。这让很多开发者在学习Python的时候有些无所适从。那么,Python对象模型有哪些特点呢?万物皆对象首先,在Python的世界里,基本类型也是对象,与通常意义上的“对象”形成有机的统一体。也就是说,Python不再区别对待原始类型和对象,所有的原始类型在内部都是由对象实现的。整数是对象,字符串也是对象:>>>a=1>>>b='abc'其次,Python中的类型也是对象,称为类型对象。整型是对象,字符串类型是对象,程序中class关键字定义的类也是对象。例如整数类型在Python内部是一个对象,称为类型对象:>>>int实例化一个整数类型得到一个整数对象,称为实例对象:>>>int('1024')1024面向对象理论中的“类”和“对象”这两个基本概念,在Python内部都是通过对象来实现的,这是Python最大的特点。类型,对象系统a是一个整数对象(instanceobject),其类型是一个整数类型(typeobject):>>>a=1>>>type(a)>>>isinstance(a,int)True那么整数类型的类型是什么?>>>type(int)可以看到integer类型的类型还是一个类型,也就是type的类型。只是这个类型比较特殊,它的实例对象还是类型对象。Python中还有一个特殊的类型object,其他所有类型都继承自object,也就是说object是所有类型的基类:>>>issubclass(int,object)True综合以上关系,如下关系图obtained:built-intypes已经想通了,自定义类型和对象关系呢?定义一个简单的类来进行实验:classDog(object):defyelp(self):print('woof')创建Dog的实例,毫无疑问是Dog类型:>>>dog=Dog()>>>dog。yelp()woof>>>type(dog)Dog类的类型自然是type,它的基类是object(即使没有显式继承):>>>type(Dog)>>>issubclass(Dog,object)True自定义子类和实例对象位于图中的什么位置?定义一个用于实验的猎犬类:classSleuth(Dog):defhunt(self):pass可以看到猎犬对象(sleuth)是猎犬类(Sleuth)的实例,Sleuth的类型为也输入:>>>sleuth=Sleuth()>>>sleuth.hunt()>>>type(sleuth)>>>type(Sleuth)同时这时,Sleuth类继承自Dog类,也就是Dog,当然也是object的子类:>>>issubclass(Sleuth,Dog)True>>>issubclass(Sleuth,object)True现在必然要讨论类型和对象这两种特殊类型。object理论上是所有类型的基类,本质上是一种类型,所以它的类型一定是type。而type是所有类型的类型,本质上是一个类型,所以它的类型一定是它自己!>>>type(object)>>>type(object)istypeTrue>>>type(type)>>>type(type)istypeTrue另外,因为object是all类型的基类理论上也是类型的基类(__base__属性):>>>issubclass(type,object)True>>>type.__base__但对象本身不能有基类。为什么?对于有继承关系的类,查找成员属性和成员方法需要追溯继承链,不断查找基类。因此,继承链必须有终点,否则就会死循环。就是这样!可以看出,所有类型的基类都收敛于object,所有类型的类型都是type,包括它自己!这是Python类型和对象系统的全貌,设计简洁、优雅、严谨。这张图将成为后续阅读源码和探索Python对象模型的利器,像地图一样指明方向。图中所有的实体在Python内部都是以对象的形式存在的。至于对象长什么样子,如何描述它们之间的关系,这些问题先不列了,后面我们会去源码中寻找答案。变量只是一个名字先看一个例子,定义一个变量a,通过id内置函数取出它的“地址”:>>>a=1>>>id(a)4302704784定义另一个变量b,给a赋值,并取出b的“地址”:>>>b=a>>>id(b)4302704784奇怪的是,a和b这两个变量的地址是一样的!这是不合理的!对于大多数语言(以C语言为例),定义一个变量a就是为其分配内存并存储变量值:变量b的内存空间独立于a,赋值时进行复制:在Python中,万物皆对象,整数也是如此。只是与对象相关联的名称:虽然变量赋值只是将当前对象与另一个名称相关联,但后面的对象是相同的:因此,在Python内部,变量只是一个名称,它包含指向实际对象的指针,并且因此绑定。变量赋值只是复制指针,而不是指针后面的对象。可变对象和不可变对象定义一个整型变量:>>>a=1>>>id(a)4302704784然后将其加1:>>>a+=1>>>a2>>>id(a)的4302704816值符合预期,但对象已更改!初学者一脸懵逼,这是什么鬼?一切都从可变对象和不可变对象开始。可变对象可以在对象创建后修改;而不可变对象在对象创建后的整个生命周期内都不能被修改。在Python中,整数类型是不可变类型,整数对象是不可变对象。当修改一个整型对象时,Python会用新的值创建一个新的对象,并将变量名绑定到新的对象上;如果没有其他引用,旧对象将被释放。每次修改整型对象都要创建新对象,回收旧对象,这样不是很低效吗?的确。后续章节将从源码的角度回答:Python是如何通过小整数池等方式进行优化的。可变对象是指创建后可以修改的对象。一个典型的例子是列表(list):>>>l=[1,2]>>>l[1,2]>>>id(l)4385900424到列表里面添加数据,发现list对象还是一样,只是多了一个元素:>>>l.append(3)>>>l[1,2,3]>>>id(l)4385900424其实里面维护了一个动态数组列表对象存储指向元素对象的指针:要从列表对象中添加或减去元素,需要修改数组。比如添加元素3:定长对象和变长对象Python一个对象有多大?相同类型的对象大小相同吗?回答类似的问题需要检查影响对象大小的因素。标准库sys模块提供函数getsizeof查看对象大小:>>>importsys>>>sys.getsizeof(1)28首先观察整型对象:>>>sys.getsizeof(1)28>>>sys.getsizeof(100000000000000000)32>>>sys.getsizeof(1000000000000000000000000000000000000000000)44可见整型对象的大小与其值有关,像这样大小不固定的对象称为变量-长度对象。我们知道,固定位数的整数所能表示的取值范围是有限的,可能会导致溢出。为了解决这个问题,Python以类似于C++中的“大整数类”的方式实现整数对象——将多个普通的32位整数串联起来,以支持更大范围的取值。至于需要多少个32位整数,要看具体值,取一个小值就可以避免浪费。这样,整数对象就需要在头部存储一些额外的信息,记录该对象使用了多少个32位整数。这是变长对象的典型结构。先有一个大概的印象就可以了,在讲解一个整型对象的源码时再展开。然后观察字符串对象:>>>sys.getsizeof('a')50>>>sys.getsizeof('abc')52字符串对象也是变长对象。这种行为很好理解,毕竟字符串的长度是无穷无尽的。另外,注意字符串对象的大小比字符串本身大,因为对象还需要维护一些额外的信息。至于具体需要维护的信息,也留给源码分析链接进行详细介绍。那么,哪些物体是固定长度的呢?——浮点数对象float:>>>sys.getsizeof(1.)24>>>sys.getsizeof(10000000000000000000000000000000.)24在浮点数后面是double的实现,即使代表的是大数,浮点数对象的大小也不变。为什么一个64位的double可以表示这么大的范围呢?答案是:以牺牲精度为代价。>>>int(100000000000000000000000000000000.)999999999999999945575230987042816由于存储的浮点数个数是固定的,它能表示的取值范围也是有限的。如果超过,就会崩溃:>>>10.**1000Traceback(最近:Filecalllast)"",line1,inOverflowError:(34,'Resulttoolarge')更多章节洞察Python虚拟机运行机制,探索高效编程之道!如何提高自己的Python开发水平,向更高的职位迈进?如果您有这些问题或疑惑,请订阅我们的专栏,阅读更多章节:内置对象虚拟机函数机制类机制生成器和协程内存管理机制转至原文以获得最佳阅读体验。订阅更新获取更多学习资料,请关注小菜学习编程: