【问题标题】:A full and minimal example for a class (not method) with Python C Extension?具有 Python C 扩展的类(不是方法)的完整和最小示例?
【发布时间】:2019-05-18 21:53:32
【问题描述】:

在任何地方,我都可以轻松找到有关使用 Python C Extensions 编写方法并在 Python 中使用它的示例。喜欢这个:Python 3 extension example

$ python3
>>> import hello
>>> hello.hello_world()
Hello, world!
>>> hello.hello('world')
Hello, world!

如何写一个hello word功能齐全的Python类(不仅仅是模块方法)?

我认为这个How to wrap a C++ object using pure Python Extension API (python3)? 问题有一个例子,但它似乎并不是最小的,因为他正在使用(或包装?)C++ 类。

例如:

class ClassName(object):
    """docstring for ClassName"""

    def __init__(self, hello):
        super().__init__()
        self.hello = hello

    def talk(self, world):
        print( '%s %s' % ( self.hello, world ) )

这个带有 C 扩展的 Python 类示例的等价物是什么?

我会这样使用它:

from .mycextensionsmodule import ClassName

classname = ClassName("Hello")
classname.talk( 'world!' )
# prints "Hello world!"

我的目标是编写一个完全用 C 语言编写的类以提高性能(我项目中的所有其他类都将使用 Python,除了这个)。我不是在寻找使用ctypes 的可移植性,也不是使用Boost.PythonSWIG 的黑匣子。只是一个纯粹用 Python C 扩展编写的高性能类。

在我得到这个Hello word 工作后,我可以在 Python Extensive 文档中找到自己的答案:

  1. https://docs.python.org/3/c-api/
  2. https://docs.python.org/3/extending/extending.html

【问题讨论】:

标签: python python-3.x class cpython python-c-api


【解决方案1】:

另请参阅:Python instance method in C

创建名为MANIFEST.in的文件

include README.md
include LICENSE.txt

recursive-include source *.h

创建名为setup.py的文件

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
from setuptools import setup, Extension

__version__ = '0.1.0'

setup(
        name = 'custom',
        version = __version__,

        package_data = {
                '': [ '**.txt', '**.md', '**.py', '**.h', '**.hpp', '**.c', '**.cpp' ],
            },

        ext_modules = [
            Extension(
                name = 'custom',
                sources = [
                    'source/custom.cpp',
                ],
                include_dirs = ['source'],
            )
        ],
    )

创建名为source/custom.cpp的文件

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "structmember.h"

typedef struct {
    PyObject_HEAD
    PyObject *first; /* first name */
    PyObject *last;  /* last name */
    int number;
} CustomObject;

static int
Custom_traverse(CustomObject *self, visitproc visit, void *arg)
{
    Py_VISIT(self->first);
    Py_VISIT(self->last);
    return 0;
}

static int
Custom_clear(CustomObject *self)
{
    Py_CLEAR(self->first);
    Py_CLEAR(self->last);
    return 0;
}

static void
Custom_dealloc(CustomObject *self)
{
    PyObject_GC_UnTrack(self);
    Custom_clear(self);
    Py_TYPE(self)->tp_free((PyObject *) self);
}

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;
}

static int
Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
{
    static char *kwlist[] = {"first", "last", "number", NULL};
    PyObject *first = NULL, *last = NULL, *tmp;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist,
                                     &first, &last,
                                     &self->number))
        return -1;

    if (first) {
        tmp = self->first;
        Py_INCREF(first);
        self->first = first;
        Py_DECREF(tmp);
    }
    if (last) {
        tmp = self->last;
        Py_INCREF(last);
        self->last = last;
        Py_DECREF(tmp);
    }
    return 0;
}

static PyMemberDef Custom_members[] = {
    {"number", T_INT, offsetof(CustomObject, number), 0,
     "custom number"},
    {NULL}  /* Sentinel */
};

static PyObject *
Custom_getfirst(CustomObject *self, void *closure)
{
    Py_INCREF(self->first);
    return self->first;
}

static int
Custom_setfirst(CustomObject *self, PyObject *value, void *closure)
{
    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute");
        return -1;
    }
    if (!PyUnicode_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "The first attribute value must be a string");
        return -1;
    }
    Py_INCREF(value);
    Py_CLEAR(self->first);
    self->first = value;
    return 0;
}

static PyObject *
Custom_getlast(CustomObject *self, void *closure)
{
    Py_INCREF(self->last);
    return self->last;
}

static int
Custom_setlast(CustomObject *self, PyObject *value, void *closure)
{
    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute");
        return -1;
    }
    if (!PyUnicode_Check(value)) {
        PyErr_SetString(PyExc_TypeError,
                        "The last attribute value must be a string");
        return -1;
    }
    Py_INCREF(value);
    Py_CLEAR(self->last);
    self->last = value;
    return 0;
}

static PyGetSetDef Custom_getsetters[] = {
    {"first", (getter) Custom_getfirst, (setter) Custom_setfirst,
     "first name", NULL},
    {"last", (getter) Custom_getlast, (setter) Custom_setlast,
     "last name", NULL},
    {NULL}  /* Sentinel */
};

static PyObject *
Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored))
{
    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 */
};

static PyTypeObject CustomType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "custom.Custom",
    .tp_doc = "Custom objects",
    .tp_basicsize = sizeof(CustomObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
    .tp_new = Custom_new,
    .tp_init = (initproc) Custom_init,
    .tp_dealloc = (destructor) Custom_dealloc,
    .tp_traverse = (traverseproc) Custom_traverse,
    .tp_clear = (inquiry) Custom_clear,
    .tp_members = Custom_members,
    .tp_methods = Custom_methods,
    .tp_getset = Custom_getsetters,
};

static PyModuleDef custommodule = {
    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);
    PyModule_AddObject(m, "Custom", (PyObject *) &CustomType);
    return m;
}

然后,要编译并安装它,您可以运行:

pip3 install . -v
python3 setup.py install

作为这个问题的旁注How to use setuptools packages and ext_modules with the same name? 不要在同一个项目中混合使用*.py 文件和 Python C 扩展,即仅使用纯 C/C++,构建 Python C 扩展而不添加 packages = [ 'package_name' ] 条目,因为它们会导致Python C Extensions 代码运行 30%,即如果程序需要 7 秒才能运行,现在使用 *.py 文件,则需要 11 秒。

参考资料:

  1. https://docs.python.org/3/extending/newtypes_tutorial.html#supporting-cyclic-garbage-collection

【讨论】:

  • 我不确定从文档中复制和粘贴示例是否有价值
  • 我这样做是因为有人对我的问题投了反对票,而 StackOveflow 每年都会删除反对票但没有接受答案的问题。当我有时间时,我应该只编辑这个答案,并按照问题的要求将其最小化。虽然我不这样做,但这个答案也直接显示了一个简单的示例以及如何使用 setuptools 和使用包含文件而不是 Python 教程中的 distriutils.core 来构建它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-15
  • 2013-02-13
  • 1970-01-01
  • 2019-11-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多