很多 Python 开发者写了很多年代码,但对 Python 的底层世界依然感觉雾里看花。
你是否思考过这些问题:
- 为什么常说“Python 中一切皆对象”,连函数和类也是对象?
- 为什么 Python 的变量不需要声明类型?
type和object到底是什么关系?为什么type(object)是type,而object又是type的父类?
如果不理解这些,你只是在用 Python 写 C 代码;理解了这些,你才能真正掌握 Python 的“动态之力”。今天,我们就深入 CPython 的源码层面,拆解 Python 的对象模型。
一、 底层解剖:PyObject 是万物之源
Python 的灵活性源于一个核心设计:所有东西在底层都是同一个结构体。
由于 CPython 是用 C 语言写的,当你创建一个整数 a = 10,或者定义一个函数 def func(): pass,在内存中它们并没有本质区别,它们都对应着 C 语言层面的一个结构体——PyObject。
每一个 Python 对象,在内存头部都至少包含两个核心字段:
ob_refcnt(引用计数):- 记录有多少个变量指向这个对象。当它变为 0 时,对象会被垃圾回收机制(GC)立即销毁。
ob_type(类型指针):- 这是一个指针,指向该对象所属的类对象(Type Object)。
- 比如整数
10的ob_type指向int类。这个指针告诉解释器:“我是一个整数,我支持加减乘除”。
结论: 无论外表多复杂,Python 对象的内核都是一个挂着“引用计数”和“类型标签”的 C 结构体。
二、 核心隐喻:变量是“便利贴”,不是“盒子”
理解对象模型的关键,在于纠正对“变量”的理解。
- 在 C/Java 中:
int a = 10;就像申请了一个名字叫a的盒子,把数字 10 放进去。赋值b = a是把 10 复制一份放到b盒子里。 - 在 Python 中:
a = 10就像在内存里吹起了一个气球(对象 10),然后拿一张写着a的便利贴(变量名)贴在气球上。- 当你执行
b = a时,不是复制气球,而是拿一张写着b的便利贴,贴在同一个气球上。
- 当你执行
这就是为什么 Python 的参数传递全是引用传递(Pass by Assignment)。这也解释了 Python 的“三位一体”特性,任何对象都有:
- Identity(身份): 内存地址(
id(obj))。 - Type(类型): 它的模具是哪个类(
type(obj))。 - Value(值): 气球里的内容。
三、 终极烧脑:type 和 object 的“鸡蛋悖论”
Python 对象模型中最令人困惑,也最精妙的设计,莫过于 type 和 object 的关系。它们构成了对象系统的时空闭环。
3.1 两个主角
object(万物之祖): 它是继承链的终点。所有的类(int,str,MyClass)默认都继承自它。它定义了对象最基本的行为(如__hash__)。type(万物之主): 它是实例化链的源头。也就是所谓的“元类”(Metaclass)。所有的类(包括object)本质上都是type创建出来的实例。
3.2 只有两句话是真的
如果你被绕晕了,只需要记住这两句“绝对真理”:
type是object的子类。 (继承维度:type也是个类,所以它得认object做父类)object是type的实例。 (实例化维度:object这个类对象,是由type制造出来的)
print(issubclass(type, object)) # True
print(isinstance(object, type)) # True
print(isinstance(type, type)) # True (自己造自己)
3.3 源码揭秘:C 语言层面的神级操作
你可能会问:这逻辑不通啊?如果是 type 造了 object,那在 type 诞生之前 object 应该不存在;但 type 又继承自 object,说明 type 诞生前 object 必须存在。这不就是死锁了吗?
在 C 语言实现的底层(CPython 源码),开发者通过精妙的指针操作解决了这个“先有鸡还是先有蛋”的问题。这是一个人工打破死循环的过程:
- 先定义结构体:
C 语言代码中,先静态定义了两个核心结构体:
PyType_Type(对应 Python 里的type)PyBaseObject_Type(对应 Python 里的object)
- 手动连接(Bootstrap):
此时它们还只是孤立的 C 结构体,编译器无法处理这种互相依赖。于是,CPython 在初始化时进行了“手动硬连线”:
- 让 type 成为自己的实例: 把
PyType_Type的ob_type指针指向它自己(&PyType_Type)。 - 让 type 继承 object: 把
PyType_Type的tp_base指针指向PyBaseObject_Type。 - 让 object 成为 type 的实例: 把
PyBaseObject_Type的ob_type指针指向PyType_Type。
- 让 type 成为自己的实例: 把
这种“我指你,你指我,我自己指我自己”的操作,在 C 语言层面完美闭合了逻辑环。
3.4 为什么这么设计?
这种看似复杂的环形设计,实际上是为了保证 Python 对象模型的一致性:
- 没有特例: 在 Python 中,一切皆对象。既然
type和object也是对象,它们就必须遵守对象的规则(有类型、有父类)。 - 逻辑闭环: 通过让两者互为依托,Python 关闭了对象系统的顶层逻辑。这确保了无论你在系统中怎么回溯,永远不会遇到一个“不是对象”的东西。
3.5 形象类比
object就像是“塑料”这种材质。type就像是“制造模具的机器”。- 源码层面的操作: 工程师先用手捏了一个“最初的机器”(静态定义的结构体),然后用这台机器造出了所有后续的模具,最后甚至给这台机器贴上了“塑料制造”的标签。
四、 动态机制:类亦是对象与属性查找
基于上述模型,Python 衍生出了极具动态特性的行为。
1. 类也是对象(First-class Citizen)
在 Python 中,class Dog: 这行代码执行完后,内存里真真切切地产生了一个名为 Dog 的对象。
正因为类是对象,所以:
- 你可以把类赋值给变量。
- 你可以把类当参数传给函数。
- 你可以在运行时动态修改类的属性(Monkey Patching)。
2. 属性查找(Attribute Lookup)
当你敲下 obj.x 时,Python 不会像 C++ 那样去偏移内存地址,而是启动了一次哈希查找:
- 先去
obj.__dict__(实例字典)里找。 - 没找到?去
obj.__class__.__dict__(类字典)里找。 - 还没找到?顺着 MRO(方法解析顺序)去父类字典里找。
- 实在没有?调用
__getattr__给你最后一次机会。
这种机制虽然比指针偏移慢,但它带来了无与伦比的灵活性。
五、 总结
Python 的对象模型是一种用空间(内存)和时间(速度)换取极致灵活性的艺术。
- 统一性: 无论是整数、函数还是类,众生平等,皆为对象。
- 元编程: 通过控制
type(元类),你可以控制类的创建过程,这是 Django ORM 等黑魔法的基石。 - 自洽性: 正是 C 语言底层那一次“精妙的指针连接”,让
type和object互为支撑,构建了一个逻辑完美自洽的动态世界。
当你下次写下 class MyClass 时,希望你能意识到:你不仅仅是在写代码,你是在指挥 type 这位造物主,用 object 这种基底材质,为你创造一个新的世界。