Python源码剖析No.2--对象模型概述

Python源码剖析–对象模型概述

引用维基百科中对python的描述

Python支持多种编程范型,包括函数式、指令式、反射式、结构化和面向对象编程。它拥有动态类型系统垃圾回收功能,能够自动管理内存使用,并且其本身拥有一个巨大而广泛的标准库。它的语言结构以及面向对象的方法旨在帮助程序员为小型的和大型的项目编写清晰的、合乎逻辑的代码。

Python中一切皆对象

什么是对象?

狭义点的解释,用类(python内置的和你自定义的)创建的实例就是对象,在python中,对象和实例在概念上可以理解为同一个东西

source:LINK

1
2
3
4
5
6
7
8
9
10
>>> i =1
>>> type(i)
<class 'int'>

>>> class demo():
... pass
...
>>> d = demo()
>>> type(d)
<class '__main__.demo'>

对象对应内存中的一块区域,这些区域存放着对应对象的信息,上述代码中 i 、d都是对应着内存中的一块区域,的d、i都是对象。

一切皆对象怎么理解?

一切皆对象,python把他们视为一类事物。相比于其他语言,python由于一切皆对象,就能做一些其他语言干不了的事情,比如直接把一个函数作为另一个函数的参数,再比如为一个函数设置属性,因为函数它在python也视若对象

source:LINK

类型对象

整数类型、浮点类型在python中也是对象,称之为类型对象

1
2
3
4
>>> int
<class 'int'>
>>> float
<class 'float'>

示例对象

可以将类型对象进行示例化,可以得到一个整数对象(浮点对象),称为实例对象

1
2
3
4
5
6
7
8
>>> int('1024')
1024
>>> float('3.14')
3.14
>>> type(1024)
<class 'int'>
>>> type(3.14)
<class 'float'>

面向对象理念中“类”和“对象”这两个基本概念在python中都是通过对象来实现的,这也是python最大的特点

type方法可以查看对象的类型,查看实例化对象的类型

1
2
3
>>> a = 1024
>>> type(a)
<class 'int'>

那么使用type查看类型对象的类型会如何呢?

1
2
3
4
>>> type(int)
<class 'type'>
>>> isinstance(int,type) //类型对象int的类型是type,相比于type函数,则是会考虑继承关系
True

可以看到,类型对象也是属于类型的,其类型是type,同样的对于float也是一致的。

那么问题给到:type类型又是什么?

1
2
>>> type(type)
<class 'type'>

结果type的类型是type,指向自身。


python中存在一个特殊类型,所有其他类型均继承于object类型,即:object类型是所有其他类型的基类,其他类型均是从object中派生出来的:

1
2
3
4
>>> issubclass(int,object)		//查看继承关系
True
>>> issubclass(type,object)
True

关系示意图:

看下自定义类的情况

1
2
3
4
5
6
7
class Dog(object):
def yelp(self):
print('woof')
dog = Dog()
print(type(Dog)) //<class 'type'>
print(isinstance(Dog,type)) //True
print(issubclass(Dog,object)) //True

再看下自定义子类的情况

1
2
3
4
5
6
7
8
class Teddy(Dog):
def sort(self):
pass

t = Teddy()
print(type(t)) //<class '__main__.Teddy'>
print(isinstance(Teddy,type)) //Trueisinstance函数考察继承关系
print(issubclass(Teddy,object)) //True

说明自定义子类的类型依旧是type,且是object的子类


type & object

深究type & object两个特殊的类型

1
2
3
4
5
6
print(type(object))
print(type(type))

#---------output---------#
<class 'type'>
<class 'type'>

可以看出,作为所有类型的类型type其类型指向其本身,作为所有类的基类的object其类型依旧是type

另外作为所有类的基类–object,理论上也是type的基类

1
issubclass(type,object)	//True

按照上述理论,object也应该是自身的基类

1
issubclass(object,object)	//True

简言之:type是所有类型的类型,type类型指向自身。object是所有类的基类,object基类指向其自身。

变量名 & 变量空间

1
2
3
4
5
6
7
8
a = 1
print(id(a))
b = a
print(id(b))

#-----------output------------#
4402723168
4402723168

上述代码中变量a、b的指向地址空间是一样的,都知道在大多数语言(C语言为例),定义变量a、b意味着为其分配内存空间用于存放变量值,b = a相当于copy一份a的值到b指向的变量空间中

然而在一切都是对象的python中,变量名仅仅是一个与对象关联的名字a = b的含义是将变量名b与变量名a指向的对象关联起来

简言之:在python中,变量名存放的是指向对象的一个指针,变量之间的赋值只是拷贝指针


可变对象&不可变对象

不可变对象指的是在对象创建后,不可对其值进行修改,若强行修改会改变变量名指向对象的变动,此为不可变的含义

可变对象指的是在对象创建后,可对其值进行修改,即便修改也不会改变变量指向的对象,此为可变的含义

以下为python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
a = 1
print(a)
print(id(a))

a += 1
print(a)
print(id(a))
#-----------python output-------------#
1
4402723168
2
4402723200

#-----------C supposed output-------------#
1
4402723168
2
4402723168

可以看到,python对于变量a做值的修正会改变变量a指向的对象,实际上在python中,对变量的值进行修正,python将为修正的结果创建新对象,并将变量名这一存放对象指针的内容修改为更正后的对象位置,原对象若没有被引用将有垃圾回收机制完成资源回收:

显然每次对变量做修改会设计修改结果后的对象创建以及可能存在的旧对象的销毁,这无疑会增加运行的开销,导致效率很低。python也存在相应的机制进行回避(后续探讨)

至此,可以知道int类型的实例对象是属于不可变对象,因为修改其值会改变变量指向的对象。

再来一个可变对象的例子,python中的列表对象:

1
2
3
4
5
6
7
8
9
10
11
12
l = [1,3]
print(l)
print(id(l))

l.append(4)
print(l)
print(id(l))
#----------------output------------#
[1, 3]
140253034107840
[1, 3, 4]
140253034107840

可以看到,往列表对象l中添加元素,会修改对象的内容,却不会对变量名l指向的对象做修改(地址不变)

实际在python中,列表对象内部维护者一个动态数组,其中存储着每一个元素对象的指针:

定长对象 & 变长对象

Python的对象占多大空间,相同类型的对象的大小是否一致..回答诸如此类问题,需要考察影响对象大小的因素

python的sys库提供了查看对象大小的函数,getsizeof,以int类型为例

1
2
3
4
5
6
7
8
9
import sys
print(sys.getsizeof(1))
print(sys.getsizeof(10000000000000))
print(sys.getsizeof(1000000000000000000000000000000000))

#-------------------output----------------#
28
32
40

由此可见,int对象的大小和其数值有关,像int这样大小不固定的对象称为变长对象

在许多语言中,以C为例,位数固定的整数能够表示的数值范围是有限的,超过这个限制范围,会造成溢出,带来诸多安全隐患。在Python中,为了解决整数溢出的问题,采取类似C++中的大整数类(以串联多个普通的整数)来实现对更大数值范围的支持,至于需要串联几个这样的整数则是要视具体需要表示的情况而定。(这就是变长的体现了)

加上前面对与可变与不可变对象的讨论中明确的int整型变量属于不可变对象,我们知道了int声明的变量指向对象为变长且不可变对象的因为int变量需要视具体数值确定所占空间的大小(变长),以及在初始化后对值的修改会指向一个存放新数值的对象(不可变)

同样的变长对象的另一个典型例子是字符串对象

1
2
3
4
5
6
7
8
print(sys.getsizeof('a'))
print(sys.getsizeof('ab'))
print(sys.getsizeof('abc'))

#------------output---------------#
50
51
52

那么定长对象有什么例子呢? – float对象

1
2
3
4
5
6
print(sys.getsizeof(1.00))
print(sys.getsizeof(100000000000000000000000000000000000000.00))

#------------output---------------#
24
24

python中的浮点数其背后是由double实现的,就算表示的数值很大,也不会改变浮点数对象所占的大小,之所以能表示这么大范围是因为其牺牲了表达的精度。但double由于位数固定,表示的范围终究有限,一旦超出范围,则会抛出异常。

Author: Victory+
Link: https://cvjark.github.io/2022/05/05/Python源码剖析-对象模型概述/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.