深度剖析:Python 自定义扩展类型开发全攻略
本文聚焦于在 Python 中运用 C 语言编写自定义扩展类型,深入介绍了从基础概念、开发流程到实际应用的全流程知识,包括关键知识点、注意事项,并结合实际项目案例,旨在帮助开发者熟练掌握这一技术,拓展 Python 编程的边界。
一、引言
Python 以其简洁高效的特性深受开发者喜爱,但在某些场景下,需要更高效的性能或特殊功能,这就需要借助 C 语言编写扩展类型来实现。自定义扩展类型能让开发者像使用 Python 内置类型(如str、list)一样操作新类型,为 Python 编程带来更大的灵活性和强大的功能。
二、Python 自定义扩展类型基础
(一)PyObject 与类型对象
在 CPython 运行时,所有 Python 对象都被视为PyObject*类型的变量,PyObject结构体包含对象的引用计数和指向类型对象的指针。类型对象决定了对象的行为,比如属性查找、方法调用、运算操作等。所以,定义新的扩展类型,关键在于创建新的类型对象。
(二)最小完整模块示例
下面展示一个最小但完整的模块,在 C 扩展模块custom中定义名为Custom的新类型:
#define PY_SSIZE_T_CLEAN
#include <Python.h>
typedef struct {
PyObject_HEAD
/* Type-specific fields go here. */
} CustomObject;
static PyTypeObject CustomType = {
.ob_base = PyVarObject_HEAD_INIT(NULL, 0),
.tp_name = "custom.Custom",
.tp_doc = PyDoc_STR("Custom objects"),
.tp_basicsize = sizeof(CustomObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_new = PyType_GenericNew
};
static PyModuleDef custommodule = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "custom",
.m_doc = "Example module that creates an extension type.",
.m_size = -1
};
PyMODINIT_FUNC PyInit_custom(void) {
PyObject *m;
if (PyType_Ready(&CustomType) < 0)
return NULL;
m = PyModule_Create(&custommodule);
if (m == NULL)
return NULL;
Py_INCREF(&CustomType);
if (PyModule_AddObject(m, "Custom", (PyObject *)&CustomType) < 0) {
Py_DECREF(&CustomType);
Py_DECREF(m);
return NULL;
}
return m;
}
该模块定义了三件事:
Custom对象的结构:CustomObject结构体,包含PyObject_HEAD,后续可添加特定字段。PyObject_HEAD是每个对象结构体的强制前缀,定义了ob_base字段,包含指向类型对象和引用计数的指针。Custom类型的行为:CustomType结构体,定义了类型的名称、文档字符串、大小、标志和创建对象的方法等。- 模块的初始化:
PyInit_custom函数及其对应的custommodule结构体,用于初始化模块并将Custom类型添加到模块字典中。
(三)关键字段解析
tp_name:类型的名称,需采用带点号的形式,包含模块名和类型名,用于对象的默认文本表示和错误消息,确保与pydoc和pickle模块兼容。tp_basicsize和tp_itemsize:tp_basicsize指定创建新实例时分配的内存大小,tp_itemsize用于可变大小的对象,通常为 0。tp_flags:设置为Py_TPFLAGS_DEFAULT,启用 Python 3.3 之前定义的全部成员。tp_new:启用对象创建的处理器,等价于 Python 的__new__()方法,这里使用PyType_GenericNew()提供的默认实现。
(四)编译与使用
将上述代码保存为custom.c,并创建pyproject.toml和setup.py文件:
收起
toml
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "custom"
version = "1"
python
from setuptools import Extension, setup
setup(ext_modules=[Extension("custom", ["custom.c"])])
在命令行执行python -m pip install.,安装后即可在 Python 中导入custom模块并使用Custom对象。
三、向基本示例添加数据和方法
(一)扩展类型定义
为使自定义类型更实用,添加数据和方法,并使其可作为基类使用。创建新模块custom2:
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <stddef.h> /* for offsetof() */
typedef struct {
PyObject_HEAD
PyObject *first; /* first name */
PyObject *last; /* last name */
int number;
} CustomObject;
在CustomObject结构体中添加了first、last(Python 字符串类型)和number(C 整数类型)三个数据属性。
(二)内存管理与对象生命周期
- 释放方法(
tp_dealloc):
static void Custom_dealloc(CustomObject* self) {
Py_XDECREF(self->first);
Py_XDECREF(self->last);
Py_TYPE(self)->tp_free((PyObject *)self);
}
该方法在对象被销毁时,负责减少first和last属性的引用计数,并调用对象类型的tp_free成员释放内存。
- 创建方法(
tp_new):
static PyObject *Custom_new(PyTypeObject* type, PyObject *args, PyObject *kwds) {
CustomObject* self;
self = (CustomObject*)type->tp_alloc(type, 0);
if (self!= NULL) {
self->first = PyUnicode_FromString("");
if (self->first == NULL) {
Py_DECREF(self);
return NULL;
}
self->last = PyUnicode_FromString("");
if (self->last == NULL) {
Py_DECREF(self);
return NULL;
}
self->number = 0;
}
return (PyObject *)self;
}
tp_new负责创建对象实例,并初始化first、last和number属性。注意,它不应显式调用tp_init,由解释器自行调用。
- 初始化方法(
tp_init):
static int Custom_init(CustomObject* self, PyObject *args, PyObject *kwds) {
static char* kwlist[] = { "first", "last", "number", NULL };
PyObject *first = NULL, *last = NULL;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist, &first, &last, &self->number))
return -1;
if (first) {
Py_XSETREF(self->first, Py_NewRef(first));
}
if (last) {
Py_XSETREF(self->last, Py_NewRef(last));
}
return 0;
}
tp_init在对象创建后进行初始化,接受位置和关键字参数,成功返回 0,失败返回 -1。由于它可能被多次调用,在处理属性赋值时需格外小心,避免引用计数和对象释放相关的问题。
(三)添加属性和方法
- 属性定义(
PyMemberDef):
static PyMemberDef Custom_members[] = {
{"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0, "first name"},
{"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0, "last name"},
{"number", Py_T_INT, offsetof(CustomObject, number), 0, "custom number"},
{NULL} /* Sentinel */
};
通过PyMemberDef结构体定义了first、last和number属性,方便在 Python 中访问和修改。
- 方法定义(
PyMethodDef):
static PyObject *Custom_name(CustomObject* self, PyObject *Py_UNUSED(ignored)) {
if (self->first == NULL) {
PyErr_SetString(PyExc_AttributeError, "first");
return NULL;
}
if (self->last == NULL) {
PyErr_SetString(PyExc_AttributeError, "last");
return NULL;
}
return PyUnicode_FromFormat("%S %S", self->first, self->last);
}
static PyMethodDef Custom_methods[] = {
{"name", (PyCFunction)Custom_name, METH_NOARGS, "Return the name, combining the first and last name"},
{NULL} /* Sentinel */
};
定义了name方法,用于返回组合后的名字。
(四)完整类型定义与模块初始化
static PyTypeObject CustomType = {
.ob_base = PyVarObject_HEAD_INIT(NULL, 0),
.tp_name = "custom2.Custom",
.tp_doc = PyDoc_STR("Custom objects"),
.tp_basicsize = sizeof(CustomObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_new = Custom_new,
.tp_init = (initproc)Custom_init,
.tp_dealloc = (destructor)Custom_dealloc,
.tp_members = Custom_members,
.tp_methods = Custom_methods
};
static PyModuleDef custommodule = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "custom2",
.m_doc = "Example module that creates an extension type.",
.m_size = -1
};
PyMODINIT_FUNC PyInit_custom2(void) {
PyObject *m;
if (PyType_Ready(&CustomType) < 0)
return NULL;
m = PyModule_Create(&custommodule);
if (m == NULL)
return NULL;
if (PyModule_AddObjectRef(m, "Custom", (PyObject *)&CustomType) < 0) {
Py_DECREF(m);
return NULL;
}
return m;
}
在CustomType结构体中,整合了上述定义的各种方法和属性,PyInit_custom2函数用于初始化模块并添加Custom类型。
四、创建自定义扩展类型的注意事项
(一)内存管理的严谨性
在处理自定义扩展类型中的数据属性时,务必正确管理内存。例如,在Custom_dealloc函数中,使用Py_XDECREF来安全地减少对象引用计数,防止内存泄漏。如果属性可能为NULL,使用Py_XDECREF而非Py_DECREF,因为前者在参数为NULL时不会导致程序崩溃。
(二)tp_new与tp_init的协作
tp_new负责创建对象实例,而tp_init用于初始化对象。开发者需明确两者的职责边界,tp_new不应调用tp_init,由解释器负责在合适的时机调用tp_init。在tp_new中分配内存后,要检查内存分配是否成功,如Custom_new函数中对tp_alloc结果的检查,避免因内存分配失败导致程序异常。
(三)属性和方法的命名规范
为确保自定义扩展类型的兼容性和可读性,属性和方法的命名应遵循 Python 的命名规范。类型名采用驼峰命名法,属性和方法名采用小写字母加下划线的方式,如first_name、get_name。同时,tp_name应使用带点号的模块名加类型名形式,保证与pydoc和pickle模块兼容。
(四)处理继承关系
若自定义类型支持继承,要注意tp_basicsize的设置。若子类和父类的tp_basicsize相同,可能会出现多重继承问题。为避免此类问题,可确保子类的tp_basicsize大于父类,或者在子类的__bases__中将父类列在最前面。
(五)异常处理
在 C 扩展代码中,合理处理异常至关重要。当函数执行失败时,应使用 Python API 中的异常设置函数(如PyErr_SetString、PyErr_SetFromErrno)来设置异常,让 Python 解释器能够捕获并处理错误。例如,在Custom_name方法中,当属性为NULL时,使用PyErr_SetString抛出AttributeError异常。
五、实际项目案例
(一)高效数据处理类库开发
在一个数据处理项目中,需要频繁对大量的地理坐标数据进行操作,包括坐标转换、距离计算等。由于 Python 原生代码在处理大规模数据时性能较低,决定使用 C 语言编写自定义扩展类型来优化性能。
- 定义坐标点类型:创建一个
Coordinate自定义扩展类型,用于表示地理坐标点。
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <math.h>
typedef struct {
PyObject_HEAD
double latitude;
double longitude;
} CoordinateObject;
static void Coordinate_dealloc(CoordinateObject* self) {
Py_TYPE(self)->tp_free((PyObject *)self);
}
static PyObject *Coordinate_new(PyTypeObject* type, PyObject *args, PyObject *kwds) {
CoordinateObject* self;
self = (CoordinateObject*)type->tp_alloc(type, 0);
if (self!= NULL) {
self->latitude = 0.0;
self->longitude = 0.0;
}
return (PyObject *)self;
}
static int Coordinate_init(CoordinateObject* self, PyObject *args, PyObject *kwds) {
static char* kwlist[] = { "latitude", "longitude", NULL };
double lat = 0.0, lon = 0.0;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|dd", kwlist, &lat, &lon))
return -1;
self->latitude = lat;
self->longitude = lon;
return 0;
}
static PyObject *Coordinate_distance(CoordinateObject* self, PyObject *args) {
CoordinateObject* other;
if (!PyArg_ParseTuple(args, "O", &other))
return NULL;
double lat1 = self->latitude * M_PI / 180.0;
double lon1 = self->longitude * M_PI / 180.0;
double lat2 = other->latitude * M_PI / 180.0;
double lon2 = other->longitude * M_PI / 180.0;
double dlon = lon2 - lon1;
double dlat = lat2 - lat1;
double a = sin(dlat / 2) * sin(dlat / 2) +
cos(lat1) * cos(lat2) * sin(dlon / 2) * sin(dlon / 2);
double c = 2 * atan2(sqrt(a), sqrt(1 - a));
double earth_radius = 6371.0; // 地球半径,单位:千米
double distance = earth_radius * c;
return PyFloat_FromDouble(distance);
}
static PyMemberDef Coordinate_members[] = {
{"latitude", Py_T_DOUBLE, offsetof(CoordinateObject, latitude), 0, "Latitude of the coordinate"},
{"longitude", Py_T_DOUBLE, offsetof(CoordinateObject, longitude), 0, "Longitude of the coordinate"},
{NULL}
};
static PyMethodDef Coordinate_methods[] = {
{"distance", (PyCFunction)Coordinate_distance, METH_VARARGS, "Calculate the distance to another coordinate"},
{NULL}
};
static PyTypeObject CoordinateType = {
.ob_base = PyVarObject_HEAD_INIT(NULL, 0),
.tp_name = "geo.Coordinate",
.tp_doc = PyDoc_STR("Represents a geographical coordinate"),
.tp_basicsize = sizeof(CoordinateObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_new = Coordinate_new,
.tp_init = (initproc)Coordinate_init,
.tp_dealloc = (destructor)Coordinate_dealloc,
.tp_members = Coordinate_members,
.tp_methods = Coordinate_methods
};
static PyModuleDef geomodule = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "geo",
.m_doc = "Module for geographical coordinate operations",
.m_size = -1
};
PyMODINIT_FUNC PyInit_geo(void) {
PyObject *m;
if (PyType_Ready(&CoordinateType) < 0)
return NULL;
m = PyModule_Create(&geomodule);
if (m == NULL)
return NULL;
if (PyModule_AddObjectRef(m, "Coordinate", (PyObject *)&CoordinateType) < 0) {
Py_DECREF(m);
return NULL;
}
return m;
}
- Python 中使用自定义扩展类型:在 Python 代码中,导入并使用
Coordinate类型进行坐标距离计算。
import geo
# 创建两个坐标点
coord1 = geo.Coordinate(latitude=30.0, longitude=120.0)
coord2 = geo.Coordinate(latitude=31.0, longitude=121.0)
# 计算距离
distance = coord1.distance(coord2)
print(f"The distance between the two coordinates is {distance} km")
通过这个自定义扩展类型,在处理大量地理坐标数据时,相比纯 Python 实现,计算效率得到显著提升,满足了项目对性能的要求。
(二)加密数据存储与处理
在一个安全相关的项目里,需要对敏感数据进行加密存储和处理。使用 C 语言编写一个自定义扩展类型EncryptedData,利用高效的 C 语言加密库(如 OpenSSL)来实现数据的加密和解密操作。
- 定义加密数据类型:
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <openssl/evp.h>
#include <string.h>
typedef struct {
PyObject_HEAD
char encrypted_data[1024];
size_t data_len;
} EncryptedDataObject;
static void EncryptedData_dealloc(EncryptedDataObject* self) {
Py_TYPE(self)->tp_free((PyObject *)self);
}
static PyObject *EncryptedData_new(PyTypeObject* type, PyObject *args, PyObject *kwds) {
EncryptedDataObject* self;
self = (EncryptedDataObject*)type->tp_alloc(type, 0);
if (self!= NULL) {
self->data_len = 0;
memset(self->encrypted_data, 0, sizeof(self->encrypted_data));
}
return (PyObject *)self;
}
static int EncryptedData_init(EncryptedDataObject* self, PyObject *args, PyObject *kwds) {
const char *plaintext;
const char *key;
if (!PyArg_ParseTuple(args, "ss", &plaintext, &key))
return -1;
EVP_CIPHER_CTX *ctx;
unsigned char outbuf[1024];
int outlen, tmplen;
ctx = EVP_CIPHER_CTX_new();
if (!ctx) {
PyErr_SetString(PyExc_RuntimeError, "Failed to create EVP context");
return -1;
}
if (1!= EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, (const unsigned char*)key, NULL)) {
PyErr_SetString(PyExc_RuntimeError, "Failed to initialize encryption");
EVP_CIPHER_CTX_free(ctx);
return -1;
}
if (1!= EVP_EncryptUpdate(ctx, outbuf, &outlen, (const unsigned char*)plaintext, strlen(plaintext))) {
PyErr_SetString(PyExc_RuntimeError, "Failed to encrypt data");
EVP_CIPHER_CTX_free(ctx);
return -1;
}
if (1!= EVP_EncryptFinal_ex(ctx, outbuf + outlen, &tmplen)) {
PyErr_SetString(PyExc_RuntimeError, "Failed to finalize encryption");
EVP_CIPHER_CTX_free(ctx);
return -1;
}
outlen += tmplen;
self->data_len = outlen;
memcpy(self->encrypted_data, outbuf, outlen);
EVP_CIPHER_CTX_free(ctx);
return 0;
}
static PyObject *EncryptedData_decrypt(EncryptedDataObject* self, PyObject *args) {
const char *key;
if (!PyArg_ParseTuple(args, "s", &key))
return NULL;
EVP_CIPHER_CTX *ctx;
unsigned char outbuf[1024];
int outlen, tmplen;
ctx = EVP_CIPHER_CTX_new();
if (!ctx) {
PyErr_SetString(PyExc_RuntimeError, "Failed to create EVP context");
return NULL;
}
if (1!= EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, (const unsigned char*)key, NULL)) {
PyErr_SetString(PyExc_RuntimeError, "Failed to initialize decryption");
EVP_CIPHER_CTX_free(ctx);
return NULL;
}
if (1!= EVP_DecryptUpdate(ctx, outbuf, &outlen, (const unsigned char*)self->encrypted_data, self->data_len)) {
PyErr_SetString(PyExc_RuntimeError, "Failed to decrypt data");
EVP_CIPHER_CTX_free(ctx);
return NULL;
}
if (1!= EVP_DecryptFinal_ex(ctx, outbuf + outlen, &tmplen)) {
PyErr_SetString(PyExc_RuntimeError, "Failed to finalize decryption");
EVP_CIPHER_CTX_free(ctx);
return NULL;
}
outlen += tmplen;
PyObject *decrypted_str = PyBytes_FromStringAndSize((const char*)outbuf, outlen);
EVP_CIPHER_CTX_free(ctx);
return decrypted_str;
}
static PyMemberDef EncryptedData_members[] = {
{"encrypted_data", Py_T_STRING, offsetof(EncryptedDataObject, encrypted_data), 0, "Encrypted data"},
{"data_len", Py_T_SIZE_T, offsetof(EncryptedDataObject, data_len), 0, "Length of encrypted data"},
{NULL}
};
static PyMethodDef EncryptedData_methods[] = {
{"decrypt", (PyCFunction)EncryptedData_decrypt, METH_VARARGS, "Decrypt the encrypted data"},
{NULL}
};
static PyTypeObject EncryptedDataType = {
.ob_base = PyVarObject_HEAD_INIT(NULL, 0),
.tp_name = "security.EncryptedData",
.tp_doc = PyDoc_STR("Represents encrypted data"),
.tp_basicsize = sizeof(EncryptedDataObject),
.tp_itemsize = 0,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_new = EncryptedData_new,
.tp_init = (initproc)EncryptedData_init,
.tp_dealloc = (destructor)EncryptedData_dealloc,
.tp_members = EncryptedData_members,
.tp_methods = EncryptedData_methods
};
static PyModuleDef securitymodule = {
.m_base = PyModuleDef_HEAD_INIT,
.m_name = "security",
.m_doc = "Module for encrypted data operations",
.m_size = -1
};
PyMODINIT_FUNC PyInit_security(void) {
PyObject *m;
if (PyType_Ready(&EncryptedDataType) < 0)
return NULL;
m = PyModule_Create(&securitymodule);
if (m == NULL)
return NULL;
if (PyModule_AddObjectRef(m, "EncryptedData", (PyObject *)&EncryptedDataType) < 0) {
Py_DECREF(m);
return NULL;
}
return m;
}
- Python 中使用加密数据类型:在 Python 代码中,导入并使用
EncryptedData类型进行数据加密和解密操作。
import security
# 加密数据
encrypted_obj = security.EncryptedData("sensitive information", "my_secret_key")
# 解密数据
decrypted_str = encrypted_obj.decrypt("my_secret_key")
print(f"The decrypted data is: {decrypted_str.decode('utf - 8')}")
通过这个自定义扩展类型,实现了高效的加密数据存储和处理功能,增强了项目的安全性。
六、总结
通过本文,我们学习了 Python 自定义扩展类型的开发流程,从基础的类型对象创建,到添加数据、方法以及内存管理和模块初始化。同时,了解了创建自定义扩展类型的注意事项,并通过实际项目案例展示了其在数据处理和安全加密等领域的应用。掌握这一技术,不仅能提升 Python 程序的性能,还能实现一些 Python 原生难以完成的任务,为 Python 编程带来更多可能。
TAG: Python 扩展开发;自定义扩展类型;C 语言扩展;Python 对象模型;内存管理;数据处理;加密技术
七、相关学习资源
- Python 官方文档:Python 官方文档 - 自定义扩展类型:教程,提供了详细的技术细节和示例代码,是深入学习的重要资料。
- Python 源码:深入研究 Python 的源代码,尤其是
Objects目录下的文件,可了解 Python 内置类型的实现方式,为自定义扩展类型开发提供参考。 - 相关书籍:《Python 扩展编程》等书籍,系统讲解了 Python 扩展开发的知识体系,有助于全面掌握相关技术。
浙公网安备 33010602011771号