云南网站建设,企业信息化软件定制开发

专业提供昆明网站建设, 昆明软件开发, 云南网站建设,企业信息化软件定制开发服务免费咨询QQ932256355

博客园 首页 新随笔 联系 订阅 管理

深度剖析:Python 自定义扩展类型开发全攻略

本文聚焦于在 Python 中运用 C 语言编写自定义扩展类型,深入介绍了从基础概念、开发流程到实际应用的全流程知识,包括关键知识点、注意事项,并结合实际项目案例,旨在帮助开发者熟练掌握这一技术,拓展 Python 编程的边界。

一、引言

Python 以其简洁高效的特性深受开发者喜爱,但在某些场景下,需要更高效的性能或特殊功能,这就需要借助 C 语言编写扩展类型来实现。自定义扩展类型能让开发者像使用 Python 内置类型(如strlist)一样操作新类型,为 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;
}

该模块定义了三件事:

  1. Custom对象的结构CustomObject结构体,包含PyObject_HEAD,后续可添加特定字段。PyObject_HEAD是每个对象结构体的强制前缀,定义了ob_base字段,包含指向类型对象和引用计数的指针。
  2. Custom类型的行为CustomType结构体,定义了类型的名称、文档字符串、大小、标志和创建对象的方法等。
  3. 模块的初始化PyInit_custom函数及其对应的custommodule结构体,用于初始化模块并将Custom类型添加到模块字典中。

(三)关键字段解析

  1. tp_name:类型的名称,需采用带点号的形式,包含模块名和类型名,用于对象的默认文本表示和错误消息,确保与pydocpickle模块兼容。
  2. tp_basicsizetp_itemsizetp_basicsize指定创建新实例时分配的内存大小,tp_itemsize用于可变大小的对象,通常为 0。
  3. tp_flags:设置为Py_TPFLAGS_DEFAULT,启用 Python 3.3 之前定义的全部成员。
  4. tp_new:启用对象创建的处理器,等价于 Python 的__new__()方法,这里使用PyType_GenericNew()提供的默认实现。

(四)编译与使用

将上述代码保存为custom.c,并创建pyproject.tomlsetup.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结构体中添加了firstlast(Python 字符串类型)和number(C 整数类型)三个数据属性。

(二)内存管理与对象生命周期

  1. 释放方法(tp_dealloc
static void Custom_dealloc(CustomObject* self) {
    Py_XDECREF(self->first);
    Py_XDECREF(self->last);
    Py_TYPE(self)->tp_free((PyObject *)self);
}

该方法在对象被销毁时,负责减少firstlast属性的引用计数,并调用对象类型的tp_free成员释放内存。

  1. 创建方法(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负责创建对象实例,并初始化firstlastnumber属性。注意,它不应显式调用tp_init,由解释器自行调用。

  1. 初始化方法(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。由于它可能被多次调用,在处理属性赋值时需格外小心,避免引用计数和对象释放相关的问题。

(三)添加属性和方法

  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结构体定义了firstlastnumber属性,方便在 Python 中访问和修改。

  1. 方法定义(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_newtp_init的协作

tp_new负责创建对象实例,而tp_init用于初始化对象。开发者需明确两者的职责边界,tp_new不应调用tp_init,由解释器负责在合适的时机调用tp_init。在tp_new中分配内存后,要检查内存分配是否成功,如Custom_new函数中对tp_alloc结果的检查,避免因内存分配失败导致程序异常。

(三)属性和方法的命名规范

为确保自定义扩展类型的兼容性和可读性,属性和方法的命名应遵循 Python 的命名规范。类型名采用驼峰命名法,属性和方法名采用小写字母加下划线的方式,如first_nameget_name。同时,tp_name应使用带点号的模块名加类型名形式,保证与pydocpickle模块兼容。

(四)处理继承关系

若自定义类型支持继承,要注意tp_basicsize的设置。若子类和父类的tp_basicsize相同,可能会出现多重继承问题。为避免此类问题,可确保子类的tp_basicsize大于父类,或者在子类的__bases__中将父类列在最前面。

(五)异常处理

在 C 扩展代码中,合理处理异常至关重要。当函数执行失败时,应使用 Python API 中的异常设置函数(如PyErr_SetStringPyErr_SetFromErrno)来设置异常,让 Python 解释器能够捕获并处理错误。例如,在Custom_name方法中,当属性为NULL时,使用PyErr_SetString抛出AttributeError异常。

五、实际项目案例

(一)高效数据处理类库开发

在一个数据处理项目中,需要频繁对大量的地理坐标数据进行操作,包括坐标转换、距离计算等。由于 Python 原生代码在处理大规模数据时性能较低,决定使用 C 语言编写自定义扩展类型来优化性能。

  1. 定义坐标点类型:创建一个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;
}
  1. 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)来实现数据的加密和解密操作。

  1. 定义加密数据类型
#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;
}
  1. 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 对象模型;内存管理;数据处理;加密技术

七、相关学习资源

  1. Python 官方文档Python 官方文档 - 自定义扩展类型:教程,提供了详细的技术细节和示例代码,是深入学习的重要资料。
  2. Python 源码:深入研究 Python 的源代码,尤其是Objects目录下的文件,可了解 Python 内置类型的实现方式,为自定义扩展类型开发提供参考。
  3. 相关书籍:《Python 扩展编程》等书籍,系统讲解了 Python 扩展开发的知识体系,有助于全面掌握相关技术。
posted on 2025-02-20 15:32  TekinTian  阅读(30)  评论(0)    收藏  举报