在开始深入讲解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 ast

code = """
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 dis

def add(a, b):
return a + b dis.dis(add)

输出类似:

    0 LOAD_FAST                0 (a)
2 LOAD_FAST 1 (b)
4 BINARY_ADD
6 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, Extension

module = 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, cdll

lib = 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_int def ctypes_add():
return lib.add(1, 2) # Python/C API版本
import example def 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)}, // strides
data.data(), // data pointer
py::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扩展。在实际工作中,要根据具体需求选择合适的方案,在性能和开发效率之间找到平衡点。

深入Python胶水语言的本质:从CPython到各类扩展机制的更多相关文章

  1. python为什么叫胶水语言?python为什么是系统脚本?

    python为什么叫胶水语言?python为什么是系统脚本?   特点是什么? python现在最广为闻名的形容大概有这些: 他是很好的胶水语言.什么是胶水语言?反正当时的我不知道. 他是新一代的系统 ...

  2. Python中高层次的数据结构,动态类型和动态绑定,使得它非常适合于快速应用开发,也适合于作为胶水语言连接已有的软件部件。

    https://github.com/jhao104/proxy_pool/blob/master/doc/introduce.md 3.代码模块 Python中高层次的数据结构,动态类型和动态绑定, ...

  3. Python的语言特性

    1.Python的函数传参 Python中所有的变量都可以理解为内存中一个对象的“引用”,或者,也可以看似C中的void *的感觉.这里记住的是类型是属于对象的,而不是变量.对象分为两种: 可更改的: ...

  4. Python中变量的本质探索

    Python中变量的本质探索 参考:Vamei博客Python进阶09 动态类型 ''' a = [1,2,3] ''' (1)这条"赋值语句"实际上是将a指向对象"[1 ...

  5. C语言的本质(28)——C语言与汇编之用汇编写一个Helloword

    为了更加深入理解C语言的本质,我们需要学习一些汇编相关的知识.作为最基本的编程语言之一,汇编语言虽然应用的范围不算很广,但是非常重要.因为它能够完成许多其它语言所无法完成的功能.就拿 Linux 内核 ...

  6. C语言的本质(15)——C语言的函数接口入门

    C语言的本质(15)--C语言的函数接口 函数的调用者和其实现者之间存在一个协议,在调用函数之前,调用者要为实现者提供某些条件,在函数返回时,实现者完成调用者需要的功能. 函数接口通过函数名,参数和返 ...

  7. C语言的本质(7)——C语言运算符大全

    C语言的本质(7)--C语言运算符大全 C语言的结合方向 C语言中各运算符的结合性分为两种,即左结合性(自左至右)和右结合性(自右至左).例如算术运算符的结合性是自左至右,即先左后右.如有表达式 x- ...

  8. C语言的本质(4)——浮点数的本质与运算

    C语言的本质(4)--浮点数的本质与运算 C语言规定了3种浮点数,float型.double型和long double型,其中float型占4个字节,double型占8个字节,longdouble型长 ...

  9. C语言的本质(3)——整数的本质与运算

    C语言的本质(3)--整数的本质与运算 计算机存储的最小单位是字节(Byte),一个字节通常是8个bit.C语言规定char型占一个字节的存储空间.如果这8个bit按无符号整数来解释,则取值范围是0~ ...

  10. 各种编程语言功能综合简要介绍(C,C++,JAVA,PHP,PYTHON,易语言)

    各种编程语言功能综合简要介绍(C,C++,JAVA,PHP,PYTHON,易语言) 总结 a.一个语言或者一个东西能火是和这种语言进入某一子行业的契机有关.也就是说这个语言有没有解决社会急需的问题. ...

随机推荐

  1. 快速部署单服务器oracle12c

    docker pull docker.io/truevoly/oracle-12c mkdir -p /usr/local/oracle/data_temp chmod 777 /usr/local/ ...

  2. 在浏览器输入 URL 回车之后发生了什么(流程图,超详细版)

    前言 这个问题已经是老生常谈了,更是经常被作为面试的压轴题出现,网上也有很多文章,但最近闲的无聊,然后就自己做了一篇笔记,感觉比之前理解更透彻了. 这篇笔记是我这两天看了数十篇文章总结出来的,所以相对 ...

  3. 常见APR攻击及其防护

    0x01 什么是ARP 地址解析协议--ARP:是根据IP地址获取物理地址的一个TCP/IP协议.主机发送信息时将包含目标IP地址的ARP请求广播到局域网络上的所有主机,并接收返回消息,以此确定目标的 ...

  4. att&ck学习笔记2

    一.环境搭建 靶场下载地址:http://vulnstack.qiyuanxuetang.net/vuln/detail/3/ DC IP:10.10.10.10OS:Windows 2012应用:A ...

  5. Python随笔之英雄联盟皮肤、炫彩爬取练习

    翻了网上爬皮肤的帖子,很多都是爬英雄的皮肤,没有获取到炫彩皮肤的文件 以下代码就是先获取所有的英雄id,再拼接成新的链接再遍历 把获取到的数据保存CSV文件到本地 (之前从事过游戏账号交易行业,还有很 ...

  6. "(UE4Editor.exe中)处有未经处理的异常:0xC0000005:读取位置0x0000000000000000时发生访问冲突"报错情况+解决方法+原因分析

    报错情况:使用ue4.27 Slate编写Widget时想通过获取Worl(通过本地PlayerController获取)来实现"设置定时任务为在音乐结束后自动触发函数"的功能 p ...

  7. [TAD] Triangles of Absolute Differences-反帕斯卡三角形

    [IMO2018] Triangles of Absolute Differences-反帕斯卡三角形 前言 叠甲 笔者不是学数竞的,在此感谢我的数竞生为我讲解题目. 笔者学艺不精,且知识面浅薄. 所 ...

  8. pycharm生成的allure测试报告如何查看本地的index.html文件?

    pycharm生成的allure测试报告应该是通过服务启动查看,但是如果把这个文件保存到本地查看,直接打开页面无内容 可以使用allure-combine工具实现本地正常打开 `from allure ...

  9. Nuxt.js 应用中的 vite:serverCreated 事件钩子

    title: Nuxt.js 应用中的 vite:serverCreated 事件钩子 date: 2024/11/18 updated: 2024/11/18 author: cmdragon ex ...

  10. 《Django 5 By Example》阅读笔记:p679-p765

    <Django 5 By Example>学习第10天,p679-p765总结,总计87页. 一.技术总结 1.channel 书里通过聊天软件功能演示Django中channel以及异步 ...