Python 是一门 面向对象 语言,实现了一个完整的面向对象体系,简洁而优雅。html
与其余面向对象编程语言相比, Python 有本身独特的一面。 这让不少开发人员在学习 Python 时,多少有些无所适从。 那么,Python 对象模型都有哪些特点呢?python
首先,在 Python 世界, 基本类型也是对象 ,与一般意义的“对象”造成一个有机统一。 换句话讲, Python 再也不区别对待基本类型和对象,全部基本类型内部均由对象实现。 一个整数是一个对象,一个字符串也是一个对象:编程
>>> a = 1 >>> b = 'abc'
其次, Python 中的 类型也是一种对象 ,称为 类型对象 。 整数类型是一个对象,字符串类型是一个对象,程序中经过 class 关键字定义的类也是一个对象。数组
举个例子,整数类型在 Python 内部是一个对象,称为 类型对象 :app
>>> int <class 'int'>
经过整数类型 实例化 能够获得一个整数对象,称为 实例对象 :编程语言
>>> int('1024') 1024
面向对象理论中的“ 类 ”和“ 对象 ”这两个基本概念,在 Python 内部都是经过对象实现的,这是 Python 最大的特色。函数
a 是一个整数对象( 实例对象 ),其类型是整数类型( 类型对象 ):工具
>>> a = 1 >>> type(a) <class 'int'> >>> isinstance(a, int) True
那么整数类型的类型又是什么呢?学习
>>> type(int) <class 'type'>
能够看到,整数类型的类型仍是一种类型,即 类型的类型 。 只是这个类型比较特殊,它的实例对象仍是类型对象。优化
Python 中还有一个特殊类型 object ,全部其余类型均继承于 object ,换句话讲 object 是全部类型的基类:
>>> issubclass(int, object) True
综合以上关系,获得如下关系图:
内置类型已经搞清楚了,自定义类型及对象关系又如何呢?定义一个简单的类来实验:
class Dog(object): def yelp(self): print('woof')
建立一个 Dog 实例,毫无疑问,其类型是 Dog :
>>> dog = Dog() >>> dog.yelp() woof >>> type(dog) <class '__main__.Dog'>
Dog 类的类型天然也是 type ,其基类是 object (就算不显式继承也是如此):
>>> type(Dog) <class 'type'> >>> issubclass(Dog, object) True
自定义子类及实例对象在图中又处于什么位置?定义一个猎犬类进行实验:
class Sleuth(Dog): def hunt(self): pass
能够看到, 猎犬对象( sleuth )是猎犬类( Sleuth )的实例, Sleuth 的类型一样是 type :
>>> sleuth = Sleuth() >>> sleuth.hunt() >>> type(sleuth) <class '__main__.Sleuth'> >>> type(Sleuth) <class 'type'>
同时, Sleuth 类继承自 Dog 类,是 Dog 的子类,固然也是 object 的子类:
>>> issubclass(Sleuth, Dog) True >>> issubclass(Sleuth, object) True
如今不可避免须要讨论 type 以及 object 这两个特殊的类型。
理论上, object 是全部类型的 基类 ,本质上是一种类型,所以其类型必然是 type 。 而 type 是全部类型的类型,本质上也是一种类型,所以其类型必须是它本身!
>>> type(object) <class 'type'> >>> type(object) is type True >>> type(type) <class 'type'> >>> type(type) is type True
另外,因为 object 是全部类型的 基类 ,理论上也是 type 的基类( __base__
属性):
>>> issubclass(type, object) True >>> type.__base__ <class 'object'>
可是 object 自身便不能有基类了。为何呢? 对于存在继承关系的类,成员属性和成员方法查找须要回溯继承链,不断查找基类。 所以,继承链必须有一个终点,否则就死循环了。
这就完整了!
能够看到,全部类型的基类收敛于 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 >>> a 2 >>> id(a) 4302704816
数值符合预期,可是对象变了!初学者一脸懵逼,这是什么鬼?
一切要从 可变对象 和 不可变对象 提及。 可变对象 在对象建立后,其值能够进行修改; 而 不可变对象 在对象建立后的整个生命周期,其值都不可修改。
在 Python 中,整数类型是不可变类型, 整数对象是不可变对象。 修改整数对象时, Python 将以新数值建立一个新对象,变量名与新对象进行绑定; 旧对象如无其余引用,将被释放。
每次修改整数对象都要建立新对象、回收旧对象,效率不是很低吗? 确实是。 后续章节将从源码角度来解答: Python 如何经过 小整数池 等手段进行优化。
可变对象是指建立后能够修改的对象,典型的例子是 列表 ( list ):
>>> l = [1, 2] >>> l [1, 2] >>> id(l) 4385900424
往列表里头追加数据,发现列表对象仍是原来那个,只不过多了一个元素了:
>>> l.append(3) >>> l [1, 2, 3] >>> id(l) 4385900424
实际上,列表对象内部维护了一个 动态数组 ,存储元素对象的指针:
列表对象增减元素,须要修改该数组。例如,追加元素 3 :
Python 一个对象多大呢?相同类型对象大小是否相同呢? 想回答相似的问题,须要考察影响对象大小的因素。
标准库 sys 模块提供了一个查看对象大小的函数 getsizeof :
>>> import sys >>> sys.getsizeof(1) 28
先观察整数对象:
>>> sys.getsizeof(1) 28 >>> sys.getsizeof(100000000000000000) 32 >>> sys.getsizeof(100000000000000000000000000000000000000000000) 44
可见整数对象的大小跟其数值有关,像这样 大小不固定 的对象称为 变长对象 。
咱们知道,位数固定的整数可以表示的数值范围是有限的,可能致使 溢出 。 Python 为解决这个问题,采用相似 C++ 中 大整数类 的思路实现整数对象 —— 串联多个普通 32 位整数,以便支持更大的数值范围。 至于须要多少个 32 位整数,则视具体数值而定,数值不大的一个足矣,避免浪费。
这样一来,整数对象须要在头部额外存储一些信息,记录对象用了多少个 32 位整数。 这就是变长对象典型的结构,先有个大概印象便可,后续讲解整数对象源码时再展开。
接着观察字符串对象:
>>> sys.getsizeof('a') 50 >>> sys.getsizeof('abc') 52
字符串对象也是变长对象,这个行为很是好理解,毕竟字符串长度不尽相同嘛。 此外,注意到字符串对象大小比字符串自己大,由于对象一样须要维护一些额外的信息。 至于具体须要维护哪些信息,一样留到源码剖析环节中详细介绍。
那么,有啥对象是定长的呢?—— 浮点数对象 float :
>>> sys.getsizeof(1.) 24 >>> sys.getsizeof(1000000000000000000000000000000000.) 24
浮点数背后是由一个 double 实现,就算表示很大的数,浮点数对象的大小也不变。
为啥 64 位的 double 能够表示这么大的范围呢?答案是:牺牲了精度。
>>> int(1000000000000000000000000000000000.) 999999999999999945575230987042816
因为浮点数存储位数是固定的,它能表示的数值范围也是有限的,超出便会抛锚:
>>> 10. ** 1000 Traceback (most recent call last): File "<stdin>", line 1, in <module> OverflowError: (34, 'Result too large')
洞悉 Python 虚拟机运行机制,探索高效程序设计之道!
到底如何才能提高个人 Python 开发水平,向更高一级的岗位迈进? 若是你有这些问题或者疑惑,请订阅咱们的专栏,阅读更多章节:
更多 Python 技术文章请访问:小菜学Python,转至 原文 可得到最佳阅读体验。
订阅更新,获取更多学习资料,请关注 小菜学编程 :