『Python底层原理』--Python对象系统探秘
Python是一种非常灵活的编程语言,它的灵活性很大程度上来自于它的对象系统。
在Python中,一切都是对象,这意味着无论是数字、字符串,还是我们自己定义的类的实例,它们在底层都遵循相同的规则。
本文尝试揭开Python的对象系统的神秘面纱。
1. 对象和类型
在Python中,每个对象都有一个类型(type),而类型本身也是一个对象。
例如,int是整数的类型,str是字符串的类型,而我们自己定义的类(class)也是一种类型。
1.1. 对象 PyObject
在CPython中,所有对象都基于一个底层的结构体PyObject。
这个结构体定义了每个对象的基本属性,比如它的引用计数(用于垃圾回收)和它的类型指针。
PyObject的定义在源码中:Include/object.h中。
// _object 就是 PyObject 的别名
typedef struct _object { };
当我们创建一个整数对象时,它实际上是一个PyLongObject,它扩展了PyObject结构体:
// _longobject 是 PyLongObject 的别名
struct _longobject {
    PyObject_HEAD
    _PyLongValue long_value;
};
_longobject定义在源码文件:Include/cpython/longintrepr.h中。
Python代码中,我们可以通过type()函数查看一个对象的类型:
a = 42
print(type(a))  # <class 'int'>
s = "hello"
print(type(s))  # <class 'str'>
1.2. 类型 PyTypeObject
类型(PyTypeObject)定义了对象的行为。
它的定义在源码:Doc/includes/typestruct.h文件中。
它的定义中包含了许多函数指针,这些函数指针决定了对象如何响应某些操作。
例如,tp_str函数指针定义了对象如何转换为字符串,
tp_as_number函数指针中则定义了加减乘除等等数值计算操作。
typedef struct _typeobject {
    PyObject_VAR_HEAD
    const char *tp_name; /* For printing, in format "<module>.<name>" */
    /* Method suites for standard classes */
    PyNumberMethods *tp_as_number;
    reprfunc tp_str;
    // 省略... ...
} PyTypeObject;
其中的PyNumberMethods在Include/cpython/object.h中有定义。
包含了各种数值计算的函数指针。
typedef struct {
    /* Number implementations must check *both*
       arguments for proper type and implement the necessary conversions
       in the slot functions themselves. */
    binaryfunc nb_add;
    binaryfunc nb_subtract;
    binaryfunc nb_multiply;
    // 省略... ...
} PyNumberMethods;
也就是说,当一个类实现了这些函数指针,那么它的对象就能执行这些操作。
2. 类的特殊方法
在Python中,我们经常使用特殊方法(如  __add__  、  __str__  等)来定义对象的行为。
实际上,这些特殊方法与上面介绍的函数指针是相互关联的。
比如,类的__add__ 方法与上一节中PyTypeObject中的PyNumberMethods中的nb_add相对应,
而__str__则与上面的tp_str相对应。
只要类实现了这些特殊方法(比如,__add__和__str__),那么,PyTypeObject中对应的函数指针就指向这些特殊方法的实现。
根据这个类创建的对象就有了相应的功能。
下面通过简单的例子来看看这个函数指针的作用。
首先,我们创建一个类,先不实现__add__和__str__。
class MyClass:
    def __init__(self, n):
        self.n = n
然后根据这个类创建一个对象,并进行加法运算:
if __name__ == "__main__":
    obj = MyClass(10)
    obj + 1
运行结果:

果然,因为PyTypeObject中PyNumberMethods字段中的nb_add对应的函数指针没有实现,无法进行加法运算。
修改MyClass类,实现__add__方法。
class MyClass:
    def __init__(self, n):
        self.n = n
    def __add__(self, m):
        self.n += m
if __name__ == "__main__":
    obj = MyClass(10)
    obj + 1
    print(obj)

这时,MyClass的对象可以正常进行加法运算了,但是print出来的结果是对象的内存地址,我们希望看到对象中n的值。
再次修改MyClass,实现__str__方法,也就是对应PyTypeObject中tp_str对应的函数指针。
class MyClass:
    def __init__(self, n):
        self.n = n
    def __add__(self, m):
        self.n += m
    def __str__(self):
        return f"当前值: {self.n}"

这样,MyClass的对象既可以进行加法运算,也可以输出可读性较好的内容。
再补充一点,子类继承父类时,会自动继承父类中已经实现的特殊方法,比如:
class MyInt(int):
    pass
a = MyInt(10)
b = MyInt(20)
print(a + b)  # 输出: 30
MyInt继承了int类型的nb_add和tp_str。
3. 类型的创建
在Python中,我们可以通过两种方式创建类型:静态定义和动态分配。
3.1. 静态定义
Python的内置类型(如int、float)是通过静态定义的方式创建的。
它们的特殊方法直接指向具体的实现函数。
int和float定义的源码分别在:Include/longobject.h和Include/floatobject.h。
3.2. 动态分配
我们自己定义的类是通过动态分配的方式创建的。
当我们使用class语句定义一个类时,Python会自动为我们设置好所有必要的特殊方法。
例如:
class MyClass:
    def __str__(self):
        return "Hello from __str__!"
obj = MyClass()
print(obj)  # 输出: Hello from __str__!
在这个例子中,MyClass的tp_str函数指针被设置为一个函数,这个函数会调用我们的 _str__方法。
4. self和方法
在Python类的定义中,方法是一种特殊的属性。
当我们通过实例调用方法时,Python会自动将实例作为第一个参数传递给方法。
class MyClass:
    def my_method(self):
        return "Hello from my_method!"
obj = MyClass()
print(obj.my_method())  # 输出: Hello from my_method!
上面的例子中,my_method是一个函数,但它通过描述符协议变成了一个方法。
当我们通过obj.my_method()调用它时,self参数会自动被设置为obj。
这里面提到的描述符协议是Python中一个非常强大的机制,它允许对象控制对属性的访问(如获取、设置和删除)。
描述符是Python中实现属性访问控制的基础,也是许多高级功能(如property、classmethod、staticmethod等)的底层实现原理。
描述符本质上就是一个包含以下方法的类:
__get__(self, instance, owner):用于访问属性时调用,instance是访问属性的实例,owner是该实例所属的类__set__(self, instance, value):用于设置属性时调用__delete__(self, instance):用于删除属性时调用
根据描述符协议,当通过对象(obj)访问my_method时,返回的是一个绑定方法,它会自动将实例对象作为self参数,这种行为是由__get__方法实现的。
5. 总结
Python对象系统的核心是PyObject和PyTypeObject。
每个对象都有一个类型,而类型定义了对象的行为,特殊方法与CPython中的函数指针之间存在直接的映射关系。
Python会根据我们定义的特殊方法自动设置函数指针,同时,特殊方法也可以从父类继承。
通过理解这些概念,我们可以更好地理解Python的动态特性和灵活性。当我们使用class定义一个类时,可以想想背后发生的故事。
『Python底层原理』--Python对象系统探秘的更多相关文章
- 操作系统底层原理与Python中socket解读
		
目录 操作系统底层原理 网络通信原理 网络基础架构 局域网与交换机/网络常见术语 OSI七层协议 TCP/IP五层模型讲解 Python中Socket模块解读 TCP协议和UDP协议 操作系统底层原理 ...
 - 『无为则无心』Python日志 — 64、Python日志模块logging介绍
		
目录 1.日志的作用 2.为什么需要写日志 3.Python中的日志处理 (1)logging模块介绍 (2)logging模块的四大组件 (3)logging日志级别 1.日志的作用 从事与软件相关 ...
 - 『无为则无心』Python基础 — 3、搭建Python开发环境
		
目录 1.Python开发环境介绍 2.Python解释器的分类 3.下载Python解释器 4.安装Python解释器 5.Python解释器验证 1.Python开发环境介绍 所谓"工欲 ...
 - 『无为则无心』Python基础 — 4、Python代码常用调试工具
		
目录 1.Python的交互模式 2.IDLE工具使用说明 3.Sublime3工具的安装与配置 (1)Sublime3的安装 (2)Sublime3的配置 4.使用Sublime编写并调试Pytho ...
 - 『无为则无心』Python函数 — 34、lambda表达式
		
目录 1.lambda的应用场景 2.lambda语法 3.快速入门 4.示例:计算a + b 5.lambda的参数形式 6.lambda的应用 lambda表达式的主要作用就是化简代码. 匿名函数 ...
 - 『无为则无心』Python面向对象 — 46、类和对象
		
目录 1.理解类和对象 2.类 3.对象 4.Python中的对象 5.类和对象的定义 (1)定义类 (2)创建对象 (3)练习 6.拓展:isinstance() 函数 1.理解类和对象 (1)类和 ...
 - python底层原理
		
有同学问到了一个问题,python中存储变量是通过内存地址来存储,那么python又是如何去判断内存中的地址是什么数据类型的呢.经过查找,找到这篇文章: 原博客地址:http://www.cnblog ...
 - Python 底层原理知识
		
1.Python是如何进行内存管理的? 答:从三个方面来说,一对象的引用计数机制,二垃圾回收机制,三内存池机制 一.对象的引用计数机制 Python内部使用引用计数,来保持追踪内存中的对象,所有对象都 ...
 - 『无为则无心』Python基础 — 2、编译型语言和解释型语言的区别
		
目录 1.什么是计算机语言 2.高级语言中的编译型语言和解释型语言 (1)编译型语言 (2)解释型语言 (3)编译型语言和解释型语言执行流程 3.知识扩展: 4.关于Python 1.什么是计算机语言 ...
 - 『无为则无心』Python面向对象 — 51、私有成员变量(类中数据的封装)
		
目录 1.私有成员变量介绍 (1)私有成员变量概念 (2)私有成员变量特点 (3)私有成员变量体验 2.属性私有化工作原理 3.定义成员变量的标识符规范 4.私有成员变量的获取和设置方式 1.私有成员 ...
 
随机推荐
- 德承工控机DA-1000 RS-485串口设置
			
由于一般情况下调试串口常使用RS-485转USB接口来进行调试,但是USB接口在长时间的调试下,接口容易松动,通讯也比较不稳定容易中断,所以改为DB9接口的RS-485来调试,稳固不松脱.抗干扰能力强 ...
 - Java Playwright 浏览器最大化
			
Playwright 是一个用于自动化 Web 应用测试的现代工具,支持多种语言(包括 Java)及多个浏览器(如 Chromium.Firefox 和 WebKit).它提供了一致的 API 来控制 ...
 - openEuler欧拉部署Redis
			
一.系统优化 关闭防火墙 systemctl stop firewalld systemctl disable firewalld 关闭selinux sed -ri 's/SELINUX=enfor ...
 - COS 音视频实践|给你的视频加把锁
			
导语 为了保障视频内容安全,防止视频被非法下载和传播,对象存储(Cloud Object Storage,COS)数据处理基于数据万象 CI 提供了 HLS 视频加密的功能,拥有相比于私有读文件更高的 ...
 - vue3 在给路由跳转增加动画之后,跳转时页面会出现上下抖动的问题
			
这个问题需要分两个步骤解决: 抖动的页面有多个多根节点 增加离开过渡的css样式 v-leave-to: {display: none} 解决步骤1 (抖动的页面有多个多根节点) 我在为路由跳转增加了 ...
 - ES6 面试题
			
新增了哪些属性? 新增块级作用域:let.const 新增数据类型:Symbol 表示独一无二的值 新增数据结构: Set,类似数组,所有数据是唯一的: Map,键值对的结合,传统的 Object 只 ...
 - Dapr-3: 从 20000 英尺之上俯瞰 Dapr
			
第 3 章 从 20000 英尺之上俯瞰 Dapr Dapr at 20,000 feet | Microsoft Docs 在第 1 章中,我们讨论了分布式微服务应用的吸引力.但是,我们也指出了它会 ...
 - JavaFx helloworld 坑
			
系统 Linux Mint IDEA 创建的 hello world 项目,用 IDEA 运行就僵住,然而用 mvn clean javafx:run 却能成功----在系统 terminal能成功, ...
 - 【前端】【探究】HTML - input类型为file时如何实现自定义文本以更好的美化
			
想到英语四级考了两次都没过,我觉得要多使用英文,所以本文使用英文书写. 本文讲述了遇到的问题,解决的思路,并讲述了解决方案,也许对你会有帮助. 目录 Problem description Solut ...
 - 【Linux】【UOS】为挂载的磁盘创建快捷方式(软链接)
			
打开项目或者保存文件的时候,如果需求路径不是系统盘路径,那么找起来还真是麻烦.以下时候通过创建快捷方式(软链接)的方式,将打开磁盘的快捷方式放在用户目录下,就方便寻找打开了. 1.查询挂载点 sudo ...