深入Python胶水语言的本质:从CPython到各类扩展机制

news/2025/1/1 18:16:28/文章来源:https://www.cnblogs.com/piperliu/p/18639538

在开始深入讲解Python如何作为胶水语言之前,我们需要先了解Python语言本身的实现机制。这对于理解Python如何与C语言交互至关重要。

CPython:Python的默认实现

当我们谈论Python时,实际上通常指的是CPython,即用C语言实现的Python解释器。这是Python的参考实现,也是最广泛使用的Python解释器。

CPython的基本架构

CPython主要包含以下几个部分:

  1. Python解释器核心
  2. 内存管理系统
  3. Python对象系统
  4. Python/C API

当我们执行一个Python程序时,大致流程是:

source code (.py文件)→ 词法分析→ 语法分析→ 生成字节码 (.pyc文件)→ Python虚拟机执行字节码

从CPython说起

要理解Python如何作为胶水语言工作,我们必须先深入了解CPython的工作机制。CPython是Python的参考实现,也是最广泛使用的Python解释器。

CPython的编译和执行过程

当我们运行一个Python程序时,实际发生了这些步骤:

  1. 词法分析
def add(a, b):return a + b

这段代码首先被分解成一系列标记(tokens):

NAME(def) NAME(add) LPAR NAME(a) COMMA NAME(b) RPAR COLON
NAME(return) NAME(a) PLUS NAME(b)
  1. 语法分析

tokens被转换为抽象语法树(AST)。你可以用Python的ast模块查看:

import astcode = """
def add(a, b):return a + b
"""tree = ast.parse(code)
print(ast.dump(tree, indent=2))"""
Module(body=[FunctionDef(name='add',args=arguments(posonlyargs=[],args=[arg(arg='a'),arg(arg='b')],kwonlyargs=[],kw_defaults=[],defaults=[]),body=[Return(value=BinOp(left=Name(id='a', ctx=Load()),op=Add(),right=Name(id='b', ctx=Load())))],decorator_list=[])],type_ignores=[])
"""
  1. 生成字节码

AST被转换为Python字节码。使用dis模块可以查看:

import disdef add(a, b):return a + bdis.dis(add)

输出类似:

    0 LOAD_FAST                0 (a)2 LOAD_FAST                1 (b)4 BINARY_ADD6 RETURN_VALUE
  1. 执行字节码

Python虚拟机(PVM)执行这些字节码。这就是为什么Python是解释型语言。

Python 虚拟机和对象系统

CPython的核心是其虚拟机和对象系统。所有Python中的数据都是对象,包括函数、类、数字等。在C层面,它们都是PyObject结构体:

typedef struct _object {Py_ssize_t ob_refcnt;        /* 引用计数 */PyTypeObject *ob_type;       /* 对象类型 */
} PyObject;

更具体的类型会扩展这个基本结构。例如,Python的整数类型:

typedef struct {PyObject_HEAD                /* 包含基本的PyObject结构 */long ob_ival;               /* 实际的整数值 */
} PyIntObject;

Python.h:连接Python和C的桥梁

Python.h是Python C API的主要头文件,它定义了与Python解释器交互所需的所有接口。当我们编写C扩展时,这个文件会:

  1. 定义所有Python类型的C表示
  2. 提供引用计数宏(Py_INCREF,Py_DECREF)
  3. 提供对象创建和操作函数
  4. 定义异常处理机制

一个简单的例子:

#include <Python.h>static PyObject* 
my_sum(PyObject *self, PyObject *args) {long a, b;/* 解析参数 */if (!PyArg_ParseTuple(args, "ll", &a, &b)) {/* 若解析失败,PyArg_ParseTuple已设置异常 */return NULL;}/* 检查溢出 */if (a > PY_LLONG_MAX - b) {PyErr_SetString(PyExc_OverflowError, "result too large");return NULL;}/* 创建并返回结果 */return PyLong_FromLong(a + b);
}

在这段代码中:

  • PyArg_ParseTuple 负责将Python参数转换为C类型
  • PyErr_SetString 设置Python异常
  • PyLong_FromLong 将C的long转换为Python的int对象

这就是Python/C API的基础。在下一部分中,我们将详细讨论各种扩展机制,包括ctypes的性能开销原理,以及numpy等库的具体实现细节。

Python调用C代码的三种主要方式

1. Python/C API:底层但强大的方式

让我们通过一个详细的例子来理解Python/C API:

// example.c
#include <Python.h>/** PyObject是Python对象在C中的表示* 所有Python对象在C中都是PyObject指针*/
static PyObject* add_numbers(PyObject* self, PyObject* args) {int a, b;// PyArg_ParseTuple解析Python传入的参数// "ii"表示期望两个整数参数if (!PyArg_ParseTuple(args, "ii", &a, &b)) {return NULL;  // 解析失败时返回NULL,Python会抛出异常}// Py_BuildValue构建Python对象并返回// "i"表示构建一个整数对象return Py_BuildValue("i", a + b);
}/* * 方法表,定义模块中的函数* 每个入口包含:{方法名, 函数指针, 参数类型标志, 文档字符串}*/
static PyMethodDef methods[] = {{"add_numbers", add_numbers, METH_VARARGS, "Add two numbers"},{NULL, NULL, 0, NULL}  // 使用NULL标记结束
};/** 模块定义结构体* 包含模块的各种信息*/
static struct PyModuleDef module = {PyModuleDef_HEAD_INIT,  // 必需的初始化宏"example",              // 模块名NULL,                   // 模块文档-1,                     // 模块状态,-1表示模块保持全局状态methods                 // 方法表
};/** 模块初始化函数* 模块被import时调用*/
PyMODINIT_FUNC PyInit_example(void) {return PyModule_Create(&module);
}

要编译这个C扩展,我们需要创建setup.py

from setuptools import setup, Extensionmodule = Extension('example',sources=['example.c'])setup(name='example',version='1.0',ext_modules=[module])

然后执行:

python setup.py build_ext --inplace

2. ctypes:Python标准库的桥梁

ctypes提供了一种更简单的方式来调用C函数:

from ctypes import cdll, c_int# 加载动态链接库
lib = cdll.LoadLibrary('./libmath.so')# 设置函数参数和返回值类型
lib.add_numbers.argtypes = [c_int, c_int]
lib.add_numbers.restype = c_int# 调用C函数
result = lib.add_numbers(1, 2)

ctypes的优势在于不需要编写C代码,但它也有一些限制:

  1. 性能开销较大
  2. 类型安全性较差
  3. 不支持复杂的数据结构

ctypes的性能开销主要来自以下几个方面:

  1. 类型转换开销
from ctypes import c_int, cdlllib = cdll.LoadLibrary('./libmath.so')# 每次调用都需要进行类型转换
result = lib.add(c_int(1), c_int(2))

当我们调用C函数时,ctypes需要:

  • 将Python对象转换为C类型
  • 调用C函数
  • 将返回值转换回Python对象

这个过程涉及多次内存分配和复制。

  1. 函数调用开销
// C代码
int add(int a, int b) {return a + b;
}
# Python代码
lib.add.argtypes = [c_int, c_int]
lib.add.restype = c_int# 每次调用都需要:
# 1. 查找函数指针
# 2. 设置参数
# 3. 调用函数
# 4. 检查错误
result = lib.add(1, 2)
  1. 动态查找开销
    ctypes需要在运行时动态查找符号,这比编译时链接慢。

比较一下性能差异:

import timeit
import ctypes# ctypes版本
lib = ctypes.CDLL('./libmath.so')
lib.add.argtypes = [ctypes.c_int, ctypes.c_int]
lib.add.restype = ctypes.c_intdef ctypes_add():return lib.add(1, 2)# Python/C API版本
import exampledef capi_add():return example.add(1, 2)# 性能测试
print("ctypes:", timeit.timeit(ctypes_add, number=1000000))
print("C API:", timeit.timeit(capi_add, number=1000000))

通常,C API版本会比ctypes快5-10倍。

3. pybind11:现代C++的最佳选择

pybind11通过模板元编程实现了优雅的接口。让我们看一个复杂点的例子:

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>namespace py = pybind11;class Matrix {
private:std::vector<double> data;size_t rows, cols;public:Matrix(size_t r, size_t c) : rows(r), cols(c), data(r * c) {}// 支持numpy数组操作py::array_t<double> as_array() {return py::array_t<double>({rows, cols}, // shape{cols * sizeof(double), sizeof(double)}, // stridesdata.data(), // data pointerpy::cast(this) // owner object);}// 矩阵乘法Matrix dot(const Matrix& other) {if (cols != other.rows)throw std::runtime_error("Dimension mismatch");Matrix result(rows, other.cols);// ... 实现矩阵乘法 ...return result;}
};PYBIND11_MODULE(example, m) {py::class_<Matrix>(m, "Matrix").def(py::init<size_t, size_t>()).def("as_array", &Matrix::as_array).def("dot", &Matrix::dot).def("__repr__",[](const Matrix& m) {return "<Matrix object>";});
}

这个例子展示了pybind11的几个重要特性:

  1. 自动类型转换
  2. 异常处理
  3. numpy集成
  4. 运算符重载

实际案例分析

1. NumPy的实现机制

NumPy的核心是ndarray,它的实现涉及多个层次:

Python层 (numpy/__init__.py, numpy/core/__init__.py等)↓
C核心层 (numpy/core/src/multiarray/*.c)↓
BLAS/LAPACK (线性代数计算库)

关键文件结构:

numpy/
├── _core/
│   ├── src/
│   │   ├── multiarray/
│   │   │   ├── array_method.c     # 数组操作的C实现
│   │   │   └── descriptor.c       # 数据类型描述符
│   │   └── umath/
│   │       └── loops.c            # 数学运算的循环实现
│   └── _multiarray_umath.pyx      # Cython接口
└── setup.py                       # 构建脚本

2. aiohttp的实现机制

aiohttp使用Cython来优化性能关键部分:

aiohttp/
├── _helpers.pyx          # Cython实现的helpers
├── _http_parser.pyx      # HTTP解析器的Cython实现
├── _http_writer.pyx      # HTTP写入器的Cython实现
└── setup.py

3. PyTorch的pybind11实现

PyTorch大量使用pybind11来暴露C++接口:

// torch/csrc/Module.cpp
PYBIND11_MODULE(torch._C, m) {py::class_<torch::Tensor>(m, "Tensor").def("backward", &torch::Tensor::backward).def("to", &torch::Tensor::to)// ... 更多方法绑定
}

总结

Python的胶水特性不是偶然的,而是精心设计的结果。从最底层的Python/C API,到便捷的ctypes,再到现代化的pybind11,Python提供了完整的解决方案谱系。

理解这些机制不仅有助于我们更好地使用Python,也能帮助我们在需要时正确选择和实现C扩展。在实际工作中,要根据具体需求选择合适的方案,在性能和开发效率之间找到平衡点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/861021.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

简答题

1 冯诺依曼结构计算机的基本思想是什么 ?按此思想设计的计算机硬件系统的应由那些部件组成,它们各有什么作用? 存储程序和程序控制是冯诺依曼结构计算机的主要设计思想。存储程序是指将解题的步骤编写为程序,然后将程序和运行程序所需要的数据以二进制的形式存放到存储器中…

基于双PI控制器和三电平SVPWM交流同步直线电机矢量控制系统的simulink建模与仿真

1.课题概述基于PSO粒子群优化的PV光伏发电系统simulink建模与仿真。通过PSO粒子群优化进行最大功率跟踪。2.系统仿真结果 3.核心程序与模型 版本:MATLAB2022a 4.系统原理简介光伏(Photovoltaic, PV)发电系统利用太阳能直接转换成电能,是实现可持续能源战略的重要组成部分。…

Gridview使用CheckBox全选与单选 Version 3

还是有网友开发ASP.NET程序,今天联系Insus.NET说,参考下面随笔,无法实现,没有效果。Gridview使用CheckBox全选与单选 Version 2 https://www.cnblogs.com/insus/archive/2013/05/22/3093114.html 几番仔细检查,放大对着搬,照抄,没能错呀!说实的,具体原因,Insus.NET…

RL中on-policy和off-policy的本质区别/重要性采样

讨论了on-policy和off-policy的本质区别。说明了off-policy MC和off-policy TD是如何利用重要性采样的,以及为什么Q-learning不需要进行重要性采样。本随笔的图片都来自UCL强化学习课程lec5 Model-free prediction的ppt (Teaching - David Silver ). 回忆值函数的表达式: \[v…

2024-2025-1 20241319 《计算机基础与程序设计》第十四周学习总结

作业信息这个作业属于哪个课程 2024-2025-1-计算机基础与程序设计这个作业要求在哪里 https://www.cnblogs.com/rocedu/p/9577842.html#WEEK14这个作业的目标 《C语言程序设计》第13章作业正文 https://www.cnblogs.com/wchxx/p/18639513**教材学习内容总结 1. 文件的打开与关闭…

视野修炼-技术周刊第115期 | 现代的 Nodejs 能力

① 一些现代的 Nodejs 能力 ② MarkItDown ③ ReactAI ④ 背景移除 ⑤ 智能图片描述生成器生成器欢迎来到第 115 期的【视野修炼 - 技术周刊】,下面是本期的精选内容简介 🔥强烈推荐一些现代的 Nodejs 能力🔧开源工具&技术资讯MarkItDown ReactAI🤖AI工具&资讯背…

2024-2025-1(20241321)《计算机基础与程序设计》第十四周学习总结

这个作业属于哪个课程 <班级的链接>(2024-2025-1-计算机基础与程序设计)这个作业要求在哪里 <作业要求的链接>(2024-2025-1计算机基础与程序设计第十四周作业)这个作业的目标 <深刻学习C语言,反思一周学习,温故知新>作业正文 ... 本博客链接https://www.…

11. 日期和时间控件

一、日期和时间控件日期和时间类也是 PySide6 中的基本类,利用它们可以设置纪年法、记录某个日期时间点、对日期时间进行计算等。用户输入日期时间及显示日期时间时需要用到日期时间控件,本节介绍有关日期时间的类及相关控件。我们可以在终端中使用 pip 安装 pyside6 模块。 …

浅析FHQ-treap

前言 更好的阅读体验 默认读者会 BST 的基本操作。 节点定义 替罪羊树采用了懒惰删除的方法,不会立即删除某个点,而是在重构时不放进数组。 struct node{ int ch[2], val; int siz1, siz2, cnt, sum; //扣去懒惰删除的节点数量,没扣去懒惰删除的节点数量,树内相同权值的…

20241313刘鸣宇《计算机基础与程序设计》第14周学习总结

2024-2025-1 20241313《计算机基础与程序设计》第14周学习总结 作业信息这个作业属于哪个课程 <班级的链接>(如2024-2025-1-计算机基础与程序设计)这个作业要求在哪里 <作业要求的链接>(如2024-2025-1计算机基础与程序设计第一周作业)这个作业的目标 <写上具…

学习笔记:旋转treap

前言 更好的阅读体验。 无旋 treap。 默认读者会 BST 的基本操作、堆和旋转。 本文旋转部分和上面那篇文章的相同。 代码中是小根堆。 思想 treap 既是一棵二叉查找树(tree),也是一个二叉堆(heap)。 但是如果这两个数据结构用同一个权值维护,那么这两种数据结构是矛盾的。…

最早发明的自平衡二叉树:AVL

前言 更好的阅读体验 默认读者会基本的 BST 操作。 节点定义 平衡因子:BF(BalanceFactor),左子树高 \(-\) 右子树高。 平衡树是让树的形态尽可能像完全二叉树,而不是链。 在 AVL 中,我们认为 \(\left|\text{BF}\right|\le 1\),也就是 BF 为 \(0,1,-1\) 时的子树是平衡的,…