[3.6] bpo-33201: Modernize "Extension types" doc (GH-6337) (GH-6412)
* bpo-33201: Modernize "Extension types" doc
* Split tutorial and other topics
* Some small fixes
* Address some review comments
* Rename noddy* to custom* and shoddy to sublist
* Fix markup.
(cherry picked from commit 1d80a56173
)
This commit is contained in:
parent
76215a4481
commit
b603609e9d
|
@ -26,9 +26,11 @@ Recommended third party tools
|
||||||
=============================
|
=============================
|
||||||
|
|
||||||
This guide only covers the basic tools for creating extensions provided
|
This guide only covers the basic tools for creating extensions provided
|
||||||
as part of this version of CPython. Third party tools like Cython,
|
as part of this version of CPython. Third party tools like
|
||||||
``cffi``, SWIG and Numba offer both simpler and more sophisticated
|
`Cython <http://cython.org/>`_, `cffi <https://cffi.readthedocs.io>`_,
|
||||||
approaches to creating C and C++ extensions for Python.
|
`SWIG <http://www.swig.org>`_ and `Numba <https://numba.pydata.org/>`_
|
||||||
|
offer both simpler and more sophisticated approaches to creating C and C++
|
||||||
|
extensions for Python.
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
|
@ -52,6 +54,7 @@ C extensions.
|
||||||
:numbered:
|
:numbered:
|
||||||
|
|
||||||
extending.rst
|
extending.rst
|
||||||
|
newtypes_tutorial.rst
|
||||||
newtypes.rst
|
newtypes.rst
|
||||||
building.rst
|
building.rst
|
||||||
windows.rst
|
windows.rst
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,896 @@
|
||||||
|
.. highlightlang:: c
|
||||||
|
|
||||||
|
.. _defining-new-types:
|
||||||
|
|
||||||
|
**********************************
|
||||||
|
Defining Extension Types: Tutorial
|
||||||
|
**********************************
|
||||||
|
|
||||||
|
.. sectionauthor:: Michael Hudson <mwh@python.net>
|
||||||
|
.. sectionauthor:: Dave Kuhlman <dkuhlman@rexx.com>
|
||||||
|
.. sectionauthor:: Jim Fulton <jim@zope.com>
|
||||||
|
|
||||||
|
|
||||||
|
Python allows the writer of a C extension module to define new types that
|
||||||
|
can be manipulated from Python code, much like the built-in :class:`str`
|
||||||
|
and :class:`list` types. The code for all extension types follows a
|
||||||
|
pattern, but there are some details that you need to understand before you
|
||||||
|
can get started. This document is a gentle introduction to the topic.
|
||||||
|
|
||||||
|
|
||||||
|
.. _dnt-basics:
|
||||||
|
|
||||||
|
The Basics
|
||||||
|
==========
|
||||||
|
|
||||||
|
The :term:`CPython` runtime sees all Python objects as variables of type
|
||||||
|
:c:type:`PyObject\*`, which serves as a "base type" for all Python objects.
|
||||||
|
The :c:type:`PyObject` structure itself only contains the object's
|
||||||
|
:term:`reference count` and a pointer to the object's "type object".
|
||||||
|
This is where the action is; the type object determines which (C) functions
|
||||||
|
get called by the interpreter when, for instance, an attribute gets looked up
|
||||||
|
on an object, a method called, or it is multiplied by another object. These
|
||||||
|
C functions are called "type methods".
|
||||||
|
|
||||||
|
So, if you want to define a new extension type, you need to create a new type
|
||||||
|
object.
|
||||||
|
|
||||||
|
This sort of thing can only be explained by example, so here's a minimal, but
|
||||||
|
complete, module that defines a new type named :class:`Custom` inside a C
|
||||||
|
extension module :mod:`custom`:
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
What we're showing here is the traditional way of defining *static*
|
||||||
|
extension types. It should be adequate for most uses. The C API also
|
||||||
|
allows defining heap-allocated extension types using the
|
||||||
|
:c:func:`PyType_FromSpec` function, which isn't covered in this tutorial.
|
||||||
|
|
||||||
|
.. literalinclude:: ../includes/custom.c
|
||||||
|
|
||||||
|
Now that's quite a bit to take in at once, but hopefully bits will seem familiar
|
||||||
|
from the previous chapter. This file defines three things:
|
||||||
|
|
||||||
|
#. What a :class:`Custom` **object** contains: this is the ``CustomObject``
|
||||||
|
struct, which is allocated once for each :class:`Custom` instance.
|
||||||
|
#. How the :class:`Custom` **type** behaves: this is the ``CustomType`` struct,
|
||||||
|
which defines a set of flags and function pointers that the interpreter
|
||||||
|
inspects when specific operations are requested.
|
||||||
|
#. How to initialize the :mod:`custom` module: this is the ``PyInit_custom``
|
||||||
|
function and the associated ``custommodule`` struct.
|
||||||
|
|
||||||
|
The first bit is::
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
PyObject_HEAD
|
||||||
|
} CustomObject;
|
||||||
|
|
||||||
|
This is what a Custom object will contain. ``PyObject_HEAD`` is mandatory
|
||||||
|
at the start of each object struct and defines a field called ``ob_base``
|
||||||
|
of type :c:type:`PyObject`, containing a pointer to a type object and a
|
||||||
|
reference count (these can be accessed using the macros :c:macro:`Py_REFCNT`
|
||||||
|
and :c:macro:`Py_TYPE` respectively). The reason for the macro is to
|
||||||
|
abstract away the layout and to enable additional fields in debug builds.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
There is no semicolon above after the :c:macro:`PyObject_HEAD` macro.
|
||||||
|
Be wary of adding one by accident: some compilers will complain.
|
||||||
|
|
||||||
|
Of course, objects generally store additional data besides the standard
|
||||||
|
``PyObject_HEAD`` boilerplate; for example, here is the definition for
|
||||||
|
standard Python floats::
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
PyObject_HEAD
|
||||||
|
double ob_fval;
|
||||||
|
} PyFloatObject;
|
||||||
|
|
||||||
|
The second bit is the definition of the type object. ::
|
||||||
|
|
||||||
|
static PyTypeObject CustomType = {
|
||||||
|
PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
|
.tp_name = "custom.Custom",
|
||||||
|
.tp_doc = "Custom objects",
|
||||||
|
.tp_basicsize = sizeof(CustomObject),
|
||||||
|
.tp_itemsize = 0,
|
||||||
|
.tp_new = PyType_GenericNew,
|
||||||
|
};
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
We recommend using C99-style designated initializers as above, to
|
||||||
|
avoid listing all the :c:type:`PyTypeObject` fields that you don't care
|
||||||
|
about and also to avoid caring about the fields' declaration order.
|
||||||
|
|
||||||
|
The actual definition of :c:type:`PyTypeObject` in :file:`object.h` has
|
||||||
|
many more :ref:`fields <type-structs>` than the definition above. The
|
||||||
|
remaining fields will be filled with zeros by the C compiler, and it's
|
||||||
|
common practice to not specify them explicitly unless you need them.
|
||||||
|
|
||||||
|
We're going to pick it apart, one field at a time::
|
||||||
|
|
||||||
|
PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
|
|
||||||
|
This line is mandatory boilerplate to initialize the ``ob_base``
|
||||||
|
field mentioned above. ::
|
||||||
|
|
||||||
|
.tp_name = "custom.Custom",
|
||||||
|
|
||||||
|
The name of our type. This will appear in the default textual representation of
|
||||||
|
our objects and in some error messages, for example:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
>>> "" + custom.Custom()
|
||||||
|
Traceback (most recent call last):
|
||||||
|
File "<stdin>", line 1, in <module>
|
||||||
|
TypeError: can only concatenate str (not "custom.Custom") to str
|
||||||
|
|
||||||
|
Note that the name is a dotted name that includes both the module name and the
|
||||||
|
name of the type within the module. The module in this case is :mod:`custom` and
|
||||||
|
the type is :class:`Custom`, so we set the type name to :class:`custom.Custom`.
|
||||||
|
Using the real dotted import path is important to make your type compatible
|
||||||
|
with the :mod:`pydoc` and :mod:`pickle` modules. ::
|
||||||
|
|
||||||
|
.tp_basicsize = sizeof(CustomObject),
|
||||||
|
.tp_itemsize = 0,
|
||||||
|
|
||||||
|
This is so that Python knows how much memory to allocate when creating
|
||||||
|
new :class:`Custom` instances. :c:member:`~PyTypeObject.tp_itemsize` is
|
||||||
|
only used for variable-sized objects and should otherwise be zero.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
If you want your type to be subclassable from Python, and your type has the same
|
||||||
|
:c:member:`~PyTypeObject.tp_basicsize` as its base type, you may have problems with multiple
|
||||||
|
inheritance. A Python subclass of your type will have to list your type first
|
||||||
|
in its :attr:`~class.__bases__`, or else it will not be able to call your type's
|
||||||
|
:meth:`__new__` method without getting an error. You can avoid this problem by
|
||||||
|
ensuring that your type has a larger value for :c:member:`~PyTypeObject.tp_basicsize` than its
|
||||||
|
base type does. Most of the time, this will be true anyway, because either your
|
||||||
|
base type will be :class:`object`, or else you will be adding data members to
|
||||||
|
your base type, and therefore increasing its size.
|
||||||
|
|
||||||
|
We set the class flags to :const:`Py_TPFLAGS_DEFAULT`. ::
|
||||||
|
|
||||||
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
|
|
||||||
|
All types should include this constant in their flags. It enables all of the
|
||||||
|
members defined until at least Python 3.3. If you need further members,
|
||||||
|
you will need to OR the corresponding flags.
|
||||||
|
|
||||||
|
We provide a doc string for the type in :c:member:`~PyTypeObject.tp_doc`. ::
|
||||||
|
|
||||||
|
.tp_doc = "Custom objects",
|
||||||
|
|
||||||
|
To enable object creation, we have to provide a :c:member:`~PyTypeObject.tp_new`
|
||||||
|
handler. This is the equivalent of the Python method :meth:`__new__`, but
|
||||||
|
has to be specified explicitly. In this case, we can just use the default
|
||||||
|
implementation provided by the API function :c:func:`PyType_GenericNew`. ::
|
||||||
|
|
||||||
|
.tp_new = PyType_GenericNew,
|
||||||
|
|
||||||
|
Everything else in the file should be familiar, except for some code in
|
||||||
|
:c:func:`PyInit_custom`::
|
||||||
|
|
||||||
|
if (PyType_Ready(&CustomType) < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
This initializes the :class:`Custom` type, filling in a number of members
|
||||||
|
to the appropriate default values, including :attr:`ob_type` that we initially
|
||||||
|
set to *NULL*. ::
|
||||||
|
|
||||||
|
PyModule_AddObject(m, "Custom", (PyObject *) &CustomType);
|
||||||
|
|
||||||
|
This adds the type to the module dictionary. This allows us to create
|
||||||
|
:class:`Custom` instances by calling the :class:`Custom` class:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
>>> import custom
|
||||||
|
>>> mycustom = custom.Custom()
|
||||||
|
|
||||||
|
That's it! All that remains is to build it; put the above code in a file called
|
||||||
|
:file:`custom.c` and:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from distutils.core import setup, Extension
|
||||||
|
setup(name="custom", version="1.0",
|
||||||
|
ext_modules=[Extension("custom", ["custom.c"])])
|
||||||
|
|
||||||
|
in a file called :file:`setup.py`; then typing
|
||||||
|
|
||||||
|
.. code-block:: shell-session
|
||||||
|
|
||||||
|
$ python setup.py build
|
||||||
|
|
||||||
|
at a shell should produce a file :file:`custom.so` in a subdirectory; move to
|
||||||
|
that directory and fire up Python --- you should be able to ``import custom`` and
|
||||||
|
play around with Custom objects.
|
||||||
|
|
||||||
|
That wasn't so hard, was it?
|
||||||
|
|
||||||
|
Of course, the current Custom type is pretty uninteresting. It has no data and
|
||||||
|
doesn't do anything. It can't even be subclassed.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
While this documentation showcases the standard :mod:`distutils` module
|
||||||
|
for building C extensions, it is recommended in real-world use cases to
|
||||||
|
use the newer and better-maintained ``setuptools`` library. Documentation
|
||||||
|
on how to do this is out of scope for this document and can be found in
|
||||||
|
the `Python Packaging User's Guide <https://packaging.python.org/tutorials/distributing-packages/>`_.
|
||||||
|
|
||||||
|
|
||||||
|
Adding data and methods to the Basic example
|
||||||
|
============================================
|
||||||
|
|
||||||
|
Let's extend the basic example to add some data and methods. Let's also make
|
||||||
|
the type usable as a base class. We'll create a new module, :mod:`custom2` that
|
||||||
|
adds these capabilities:
|
||||||
|
|
||||||
|
.. literalinclude:: ../includes/custom2.c
|
||||||
|
|
||||||
|
|
||||||
|
This version of the module has a number of changes.
|
||||||
|
|
||||||
|
We've added an extra include::
|
||||||
|
|
||||||
|
#include <structmember.h>
|
||||||
|
|
||||||
|
This include provides declarations that we use to handle attributes, as
|
||||||
|
described a bit later.
|
||||||
|
|
||||||
|
The :class:`Custom` type now has three data attributes in its C struct,
|
||||||
|
*first*, *last*, and *number*. The *first* and *last* variables are Python
|
||||||
|
strings containing first and last names. The *number* attribute is a C integer.
|
||||||
|
|
||||||
|
The object structure is updated accordingly::
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
PyObject_HEAD
|
||||||
|
PyObject *first; /* first name */
|
||||||
|
PyObject *last; /* last name */
|
||||||
|
int number;
|
||||||
|
} CustomObject;
|
||||||
|
|
||||||
|
Because we now have data to manage, we have to be more careful about object
|
||||||
|
allocation and deallocation. At a minimum, we need a deallocation method::
|
||||||
|
|
||||||
|
static void
|
||||||
|
Custom_dealloc(CustomObject *self)
|
||||||
|
{
|
||||||
|
Py_XDECREF(self->first);
|
||||||
|
Py_XDECREF(self->last);
|
||||||
|
Py_TYPE(self)->tp_free((PyObject *) self);
|
||||||
|
}
|
||||||
|
|
||||||
|
which is assigned to the :c:member:`~PyTypeObject.tp_dealloc` member::
|
||||||
|
|
||||||
|
.tp_dealloc = (destructor) Custom_dealloc,
|
||||||
|
|
||||||
|
This method first clears the reference counts of the two Python attributes.
|
||||||
|
:c:func:`Py_XDECREF` correctly handles the case where its argument is
|
||||||
|
*NULL* (which might happen here if ``tp_new`` failed midway). It then
|
||||||
|
calls the :c:member:`~PyTypeObject.tp_free` member of the object's type
|
||||||
|
(computed by ``Py_TYPE(self)``) to free the object's memory. Note that
|
||||||
|
the object's type might not be :class:`CustomType`, because the object may
|
||||||
|
be an instance of a subclass.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The explicit cast to ``destructor`` above is needed because we defined
|
||||||
|
``Custom_dealloc`` to take a ``CustomObject *`` argument, but the ``tp_dealloc``
|
||||||
|
function pointer expects to receive a ``PyObject *`` argument. Otherwise,
|
||||||
|
the compiler will emit a warning. This is object-oriented polymorphism,
|
||||||
|
in C!
|
||||||
|
|
||||||
|
We want to make sure that the first and last names are initialized to empty
|
||||||
|
strings, so we provide a ``tp_new`` implementation::
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
and install it in the :c:member:`~PyTypeObject.tp_new` member::
|
||||||
|
|
||||||
|
.tp_new = Custom_new,
|
||||||
|
|
||||||
|
The ``tp_new`` handler is responsible for creating (as opposed to initializing)
|
||||||
|
objects of the type. It is exposed in Python as the :meth:`__new__` method.
|
||||||
|
It is not required to define a ``tp_new`` member, and indeed many extension
|
||||||
|
types will simply reuse :c:func:`PyType_GenericNew` as done in the first
|
||||||
|
version of the ``Custom`` type above. In this case, we use the ``tp_new``
|
||||||
|
handler to initialize the ``first`` and ``last`` attributes to non-*NULL*
|
||||||
|
default values.
|
||||||
|
|
||||||
|
``tp_new`` is passed the type being instantiated (not necessarily ``CustomType``,
|
||||||
|
if a subclass is instantiated) and any arguments passed when the type was
|
||||||
|
called, and is expected to return the instance created. ``tp_new`` handlers
|
||||||
|
always accept positional and keyword arguments, but they often ignore the
|
||||||
|
arguments, leaving the argument handling to initializer (a.k.a. ``tp_init``
|
||||||
|
in C or ``__init__`` in Python) methods.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
``tp_new`` shouldn't call ``tp_init`` explicitly, as the interpreter
|
||||||
|
will do it itself.
|
||||||
|
|
||||||
|
The ``tp_new`` implementation calls the :c:member:`~PyTypeObject.tp_alloc`
|
||||||
|
slot to allocate memory::
|
||||||
|
|
||||||
|
self = (CustomObject *) type->tp_alloc(type, 0);
|
||||||
|
|
||||||
|
Since memory allocation may fail, we must check the :c:member:`~PyTypeObject.tp_alloc`
|
||||||
|
result against *NULL* before proceeding.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
We didn't fill the :c:member:`~PyTypeObject.tp_alloc` slot ourselves. Rather
|
||||||
|
:c:func:`PyType_Ready` fills it for us by inheriting it from our base class,
|
||||||
|
which is :class:`object` by default. Most types use the default allocation
|
||||||
|
strategy.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
If you are creating a co-operative :c:member:`~PyTypeObject.tp_new` (one
|
||||||
|
that calls a base type's :c:member:`~PyTypeObject.tp_new` or :meth:`__new__`),
|
||||||
|
you must *not* try to determine what method to call using method resolution
|
||||||
|
order at runtime. Always statically determine what type you are going to
|
||||||
|
call, and call its :c:member:`~PyTypeObject.tp_new` directly, or via
|
||||||
|
``type->tp_base->tp_new``. If you do not do this, Python subclasses of your
|
||||||
|
type that also inherit from other Python-defined classes may not work correctly.
|
||||||
|
(Specifically, you may not be able to create instances of such subclasses
|
||||||
|
without getting a :exc:`TypeError`.)
|
||||||
|
|
||||||
|
We also define an initialization function which accepts arguments to provide
|
||||||
|
initial values for our instance::
|
||||||
|
|
||||||
|
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, "|OOi", kwlist,
|
||||||
|
&first, &last,
|
||||||
|
&self->number))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (first) {
|
||||||
|
tmp = self->first;
|
||||||
|
Py_INCREF(first);
|
||||||
|
self->first = first;
|
||||||
|
Py_XDECREF(tmp);
|
||||||
|
}
|
||||||
|
if (last) {
|
||||||
|
tmp = self->last;
|
||||||
|
Py_INCREF(last);
|
||||||
|
self->last = last;
|
||||||
|
Py_XDECREF(tmp);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
by filling the :c:member:`~PyTypeObject.tp_init` slot. ::
|
||||||
|
|
||||||
|
.tp_init = (initproc) Custom_init,
|
||||||
|
|
||||||
|
The :c:member:`~PyTypeObject.tp_init` slot is exposed in Python as the
|
||||||
|
:meth:`__init__` method. It is used to initialize an object after it's
|
||||||
|
created. Initializers always accept positional and keyword arguments,
|
||||||
|
and they should return either ``0`` on success or ``-1`` on error.
|
||||||
|
|
||||||
|
Unlike the ``tp_new`` handler, there is no guarantee that ``tp_init``
|
||||||
|
is called at all (for example, the :mod:`pickle` module by default
|
||||||
|
doesn't call :meth:`__init__` on unpickled instances). It can also be
|
||||||
|
called multiple times. Anyone can call the :meth:`__init__` method on
|
||||||
|
our objects. For this reason, we have to be extra careful when assigning
|
||||||
|
the new attribute values. We might be tempted, for example to assign the
|
||||||
|
``first`` member like this::
|
||||||
|
|
||||||
|
if (first) {
|
||||||
|
Py_XDECREF(self->first);
|
||||||
|
Py_INCREF(first);
|
||||||
|
self->first = first;
|
||||||
|
}
|
||||||
|
|
||||||
|
But this would be risky. Our type doesn't restrict the type of the
|
||||||
|
``first`` member, so it could be any kind of object. It could have a
|
||||||
|
destructor that causes code to be executed that tries to access the
|
||||||
|
``first`` member; or that destructor could release the
|
||||||
|
:term:`Global interpreter Lock` and let arbitrary code run in other
|
||||||
|
threads that accesses and modifies our object.
|
||||||
|
|
||||||
|
To be paranoid and protect ourselves against this possibility, we almost
|
||||||
|
always reassign members before decrementing their reference counts. When
|
||||||
|
don't we have to do this?
|
||||||
|
|
||||||
|
* when we absolutely know that the reference count is greater than 1;
|
||||||
|
|
||||||
|
* when we know that deallocation of the object [#]_ will neither release
|
||||||
|
the :term:`GIL` nor cause any calls back into our type's code;
|
||||||
|
|
||||||
|
* when decrementing a reference count in a :c:member:`~PyTypeObject.tp_dealloc`
|
||||||
|
handler on a type which doesn't support cyclic garbage collection [#]_.
|
||||||
|
|
||||||
|
We want to expose our instance variables as attributes. There are a
|
||||||
|
number of ways to do that. The simplest way is to define member definitions::
|
||||||
|
|
||||||
|
static PyMemberDef Custom_members[] = {
|
||||||
|
{"first", T_OBJECT_EX, offsetof(CustomObject, first), 0,
|
||||||
|
"first name"},
|
||||||
|
{"last", T_OBJECT_EX, offsetof(CustomObject, last), 0,
|
||||||
|
"last name"},
|
||||||
|
{"number", T_INT, offsetof(CustomObject, number), 0,
|
||||||
|
"custom number"},
|
||||||
|
{NULL} /* Sentinel */
|
||||||
|
};
|
||||||
|
|
||||||
|
and put the definitions in the :c:member:`~PyTypeObject.tp_members` slot::
|
||||||
|
|
||||||
|
.tp_members = Custom_members,
|
||||||
|
|
||||||
|
Each member definition has a member name, type, offset, access flags and
|
||||||
|
documentation string. See the :ref:`Generic-Attribute-Management` section
|
||||||
|
below for details.
|
||||||
|
|
||||||
|
A disadvantage of this approach is that it doesn't provide a way to restrict the
|
||||||
|
types of objects that can be assigned to the Python attributes. We expect the
|
||||||
|
first and last names to be strings, but any Python objects can be assigned.
|
||||||
|
Further, the attributes can be deleted, setting the C pointers to *NULL*. Even
|
||||||
|
though we can make sure the members are initialized to non-*NULL* values, the
|
||||||
|
members can be set to *NULL* if the attributes are deleted.
|
||||||
|
|
||||||
|
We define a single method, :meth:`Custom.name()`, that outputs the objects name as the
|
||||||
|
concatenation of the first and last names. ::
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
Custom_name(CustomObject *self)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
The method is implemented as a C function that takes a :class:`Custom` (or
|
||||||
|
:class:`Custom` subclass) instance as the first argument. Methods always take an
|
||||||
|
instance as the first argument. Methods often take positional and keyword
|
||||||
|
arguments as well, but in this case we don't take any and don't need to accept
|
||||||
|
a positional argument tuple or keyword argument dictionary. This method is
|
||||||
|
equivalent to the Python method:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
def name(self):
|
||||||
|
return "%s %s" % (self.first, self.last)
|
||||||
|
|
||||||
|
Note that we have to check for the possibility that our :attr:`first` and
|
||||||
|
:attr:`last` members are *NULL*. This is because they can be deleted, in which
|
||||||
|
case they are set to *NULL*. It would be better to prevent deletion of these
|
||||||
|
attributes and to restrict the attribute values to be strings. We'll see how to
|
||||||
|
do that in the next section.
|
||||||
|
|
||||||
|
Now that we've defined the method, we need to create an array of method
|
||||||
|
definitions::
|
||||||
|
|
||||||
|
static PyMethodDef Custom_methods[] = {
|
||||||
|
{"name", (PyCFunction) Custom_name, METH_NOARGS,
|
||||||
|
"Return the name, combining the first and last name"
|
||||||
|
},
|
||||||
|
{NULL} /* Sentinel */
|
||||||
|
};
|
||||||
|
|
||||||
|
(note that we used the :const:`METH_NOARGS` flag to indicate that the method
|
||||||
|
is expecting no arguments other than *self*)
|
||||||
|
|
||||||
|
and assign it to the :c:member:`~PyTypeObject.tp_methods` slot::
|
||||||
|
|
||||||
|
.tp_methods = Custom_methods,
|
||||||
|
|
||||||
|
Finally, we'll make our type usable as a base class for subclassing. We've
|
||||||
|
written our methods carefully so far so that they don't make any assumptions
|
||||||
|
about the type of the object being created or used, so all we need to do is
|
||||||
|
to add the :const:`Py_TPFLAGS_BASETYPE` to our class flag definition::
|
||||||
|
|
||||||
|
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
|
||||||
|
|
||||||
|
We rename :c:func:`PyInit_custom` to :c:func:`PyInit_custom2`, update the
|
||||||
|
module name in the :c:type:`PyModuleDef` struct, and update the full class
|
||||||
|
name in the :c:type:`PyTypeObject` struct.
|
||||||
|
|
||||||
|
Finally, we update our :file:`setup.py` file to build the new module:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from distutils.core import setup, Extension
|
||||||
|
setup(name="custom", version="1.0",
|
||||||
|
ext_modules=[
|
||||||
|
Extension("custom", ["custom.c"]),
|
||||||
|
Extension("custom2", ["custom2.c"]),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
Providing finer control over data attributes
|
||||||
|
============================================
|
||||||
|
|
||||||
|
In this section, we'll provide finer control over how the :attr:`first` and
|
||||||
|
:attr:`last` attributes are set in the :class:`Custom` example. In the previous
|
||||||
|
version of our module, the instance variables :attr:`first` and :attr:`last`
|
||||||
|
could be set to non-string values or even deleted. We want to make sure that
|
||||||
|
these attributes always contain strings.
|
||||||
|
|
||||||
|
.. literalinclude:: ../includes/custom3.c
|
||||||
|
|
||||||
|
|
||||||
|
To provide greater control, over the :attr:`first` and :attr:`last` attributes,
|
||||||
|
we'll use custom getter and setter functions. Here are the functions for
|
||||||
|
getting and setting the :attr:`first` attribute::
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
PyObject *tmp;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
tmp = self->first;
|
||||||
|
Py_INCREF(value);
|
||||||
|
self->first = value;
|
||||||
|
Py_DECREF(tmp);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
The getter function is passed a :class:`Custom` object and a "closure", which is
|
||||||
|
a void pointer. In this case, the closure is ignored. (The closure supports an
|
||||||
|
advanced usage in which definition data is passed to the getter and setter. This
|
||||||
|
could, for example, be used to allow a single set of getter and setter functions
|
||||||
|
that decide the attribute to get or set based on data in the closure.)
|
||||||
|
|
||||||
|
The setter function is passed the :class:`Custom` object, the new value, and the
|
||||||
|
closure. The new value may be *NULL*, in which case the attribute is being
|
||||||
|
deleted. In our setter, we raise an error if the attribute is deleted or if its
|
||||||
|
new value is not a string.
|
||||||
|
|
||||||
|
We create an array of :c:type:`PyGetSetDef` structures::
|
||||||
|
|
||||||
|
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 */
|
||||||
|
};
|
||||||
|
|
||||||
|
and register it in the :c:member:`~PyTypeObject.tp_getset` slot::
|
||||||
|
|
||||||
|
.tp_getset = Custom_getsetters,
|
||||||
|
|
||||||
|
The last item in a :c:type:`PyGetSetDef` structure is the "closure" mentioned
|
||||||
|
above. In this case, we aren't using a closure, so we just pass *NULL*.
|
||||||
|
|
||||||
|
We also remove the member definitions for these attributes::
|
||||||
|
|
||||||
|
static PyMemberDef Custom_members[] = {
|
||||||
|
{"number", T_INT, offsetof(CustomObject, number), 0,
|
||||||
|
"custom number"},
|
||||||
|
{NULL} /* Sentinel */
|
||||||
|
};
|
||||||
|
|
||||||
|
We also need to update the :c:member:`~PyTypeObject.tp_init` handler to only
|
||||||
|
allow strings [#]_ to be passed::
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
With these changes, we can assure that the ``first`` and ``last`` members are
|
||||||
|
never *NULL* so we can remove checks for *NULL* values in almost all cases.
|
||||||
|
This means that most of the :c:func:`Py_XDECREF` calls can be converted to
|
||||||
|
:c:func:`Py_DECREF` calls. The only place we can't change these calls is in
|
||||||
|
the ``tp_dealloc`` implementation, where there is the possibility that the
|
||||||
|
initialization of these members failed in ``tp_new``.
|
||||||
|
|
||||||
|
We also rename the module initialization function and module name in the
|
||||||
|
initialization function, as we did before, and we add an extra definition to the
|
||||||
|
:file:`setup.py` file.
|
||||||
|
|
||||||
|
|
||||||
|
Supporting cyclic garbage collection
|
||||||
|
====================================
|
||||||
|
|
||||||
|
Python has a :term:`cyclic garbage collector (GC) <garbage collection>` that
|
||||||
|
can identify unneeded objects even when their reference counts are not zero.
|
||||||
|
This can happen when objects are involved in cycles. For example, consider:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
>>> l = []
|
||||||
|
>>> l.append(l)
|
||||||
|
>>> del l
|
||||||
|
|
||||||
|
In this example, we create a list that contains itself. When we delete it, it
|
||||||
|
still has a reference from itself. Its reference count doesn't drop to zero.
|
||||||
|
Fortunately, Python's cyclic garbage collector will eventually figure out that
|
||||||
|
the list is garbage and free it.
|
||||||
|
|
||||||
|
In the second version of the :class:`Custom` example, we allowed any kind of
|
||||||
|
object to be stored in the :attr:`first` or :attr:`last` attributes [#]_.
|
||||||
|
Besides, in the second and third versions, we allowed subclassing
|
||||||
|
:class:`Custom`, and subclasses may add arbitrary attributes. For any of
|
||||||
|
those two reasons, :class:`Custom` objects can participate in cycles:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
>>> import custom3
|
||||||
|
>>> class Derived(custom3.Custom): pass
|
||||||
|
...
|
||||||
|
>>> n = Derived()
|
||||||
|
>>> n.some_attribute = n
|
||||||
|
|
||||||
|
To allow a :class:`Custom` instance participating in a reference cycle to
|
||||||
|
be properly detected and collected by the cyclic GC, our :class:`Custom` type
|
||||||
|
needs to fill two additional slots and to enable a flag that enables these slots:
|
||||||
|
|
||||||
|
.. literalinclude:: ../includes/custom4.c
|
||||||
|
|
||||||
|
|
||||||
|
First, the traversal method lets the cyclic GC know about subobjects that could
|
||||||
|
participate in cycles::
|
||||||
|
|
||||||
|
static int
|
||||||
|
Custom_traverse(CustomObject *self, visitproc visit, void *arg)
|
||||||
|
{
|
||||||
|
int vret;
|
||||||
|
if (self->first) {
|
||||||
|
vret = visit(self->first, arg);
|
||||||
|
if (vret != 0)
|
||||||
|
return vret;
|
||||||
|
}
|
||||||
|
if (self->last) {
|
||||||
|
vret = visit(self->last, arg);
|
||||||
|
if (vret != 0)
|
||||||
|
return vret;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
For each subobject that can participate in cycles, we need to call the
|
||||||
|
:c:func:`visit` function, which is passed to the traversal method. The
|
||||||
|
:c:func:`visit` function takes as arguments the subobject and the extra argument
|
||||||
|
*arg* passed to the traversal method. It returns an integer value that must be
|
||||||
|
returned if it is non-zero.
|
||||||
|
|
||||||
|
Python provides a :c:func:`Py_VISIT` macro that automates calling visit
|
||||||
|
functions. With :c:func:`Py_VISIT`, we can minimize the amount of boilerplate
|
||||||
|
in ``Custom_traverse``::
|
||||||
|
|
||||||
|
static int
|
||||||
|
Custom_traverse(CustomObject *self, visitproc visit, void *arg)
|
||||||
|
{
|
||||||
|
Py_VISIT(self->first);
|
||||||
|
Py_VISIT(self->last);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The :c:member:`~PyTypeObject.tp_traverse` implementation must name its
|
||||||
|
arguments exactly *visit* and *arg* in order to use :c:func:`Py_VISIT`.
|
||||||
|
|
||||||
|
Second, we need to provide a method for clearing any subobjects that can
|
||||||
|
participate in cycles::
|
||||||
|
|
||||||
|
static int
|
||||||
|
Custom_clear(CustomObject *self)
|
||||||
|
{
|
||||||
|
Py_CLEAR(self->first);
|
||||||
|
Py_CLEAR(self->last);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Notice the use of the :c:func:`Py_CLEAR` macro. It is the recommended and safe
|
||||||
|
way to clear data attributes of arbitrary types while decrementing
|
||||||
|
their reference counts. If you were to call :c:func:`Py_XDECREF` instead
|
||||||
|
on the attribute before setting it to *NULL*, there is a possibility
|
||||||
|
that the attribute's destructor would call back into code that reads the
|
||||||
|
attribute again (*especially* if there is a reference cycle).
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
You could emulate :c:func:`Py_CLEAR` by writing::
|
||||||
|
|
||||||
|
PyObject *tmp;
|
||||||
|
tmp = self->first;
|
||||||
|
self->first = NULL;
|
||||||
|
Py_XDECREF(tmp);
|
||||||
|
|
||||||
|
Nevertheless, it is much easier and less error-prone to always
|
||||||
|
use :c:func:`Py_CLEAR` when deleting an attribute. Don't
|
||||||
|
try to micro-optimize at the expense of robustness!
|
||||||
|
|
||||||
|
The deallocator ``Custom_dealloc`` may call arbitrary code when clearing
|
||||||
|
attributes. It means the circular GC can be triggered inside the function.
|
||||||
|
Since the GC assumes reference count is not zero, we need to untrack the object
|
||||||
|
from the GC by calling :c:func:`PyObject_GC_UnTrack` before clearing members.
|
||||||
|
Here is our reimplemented deallocator using :c:func:`PyObject_GC_UnTrack`
|
||||||
|
and ``Custom_clear``::
|
||||||
|
|
||||||
|
static void
|
||||||
|
Custom_dealloc(CustomObject *self)
|
||||||
|
{
|
||||||
|
PyObject_GC_UnTrack(self);
|
||||||
|
Custom_clear(self);
|
||||||
|
Py_TYPE(self)->tp_free((PyObject *) self);
|
||||||
|
}
|
||||||
|
|
||||||
|
Finally, we add the :const:`Py_TPFLAGS_HAVE_GC` flag to the class flags::
|
||||||
|
|
||||||
|
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC,
|
||||||
|
|
||||||
|
That's pretty much it. If we had written custom :c:member:`~PyTypeObject.tp_alloc` or
|
||||||
|
:c:member:`~PyTypeObject.tp_free` handlers, we'd need to modify them for cyclic
|
||||||
|
garbage collection. Most extensions will use the versions automatically provided.
|
||||||
|
|
||||||
|
|
||||||
|
Subclassing other types
|
||||||
|
=======================
|
||||||
|
|
||||||
|
It is possible to create new extension types that are derived from existing
|
||||||
|
types. It is easiest to inherit from the built in types, since an extension can
|
||||||
|
easily use the :c:type:`PyTypeObject` it needs. It can be difficult to share
|
||||||
|
these :c:type:`PyTypeObject` structures between extension modules.
|
||||||
|
|
||||||
|
In this example we will create a :class:`SubList` type that inherits from the
|
||||||
|
built-in :class:`list` type. The new type will be completely compatible with
|
||||||
|
regular lists, but will have an additional :meth:`increment` method that
|
||||||
|
increases an internal counter:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
>>> import sublist
|
||||||
|
>>> s = sublist.SubList(range(3))
|
||||||
|
>>> s.extend(s)
|
||||||
|
>>> print(len(s))
|
||||||
|
6
|
||||||
|
>>> print(s.increment())
|
||||||
|
1
|
||||||
|
>>> print(s.increment())
|
||||||
|
2
|
||||||
|
|
||||||
|
.. literalinclude:: ../includes/sublist.c
|
||||||
|
|
||||||
|
|
||||||
|
As you can see, the source code closely resembles the :class:`Custom` examples in
|
||||||
|
previous sections. We will break down the main differences between them. ::
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
PyListObject list;
|
||||||
|
int state;
|
||||||
|
} SubListObject;
|
||||||
|
|
||||||
|
The primary difference for derived type objects is that the base type's
|
||||||
|
object structure must be the first value. The base type will already include
|
||||||
|
the :c:func:`PyObject_HEAD` at the beginning of its structure.
|
||||||
|
|
||||||
|
When a Python object is a :class:`SubList` instance, its ``PyObject *`` pointer
|
||||||
|
can be safely cast to both ``PyListObject *`` and ``SubListObject *``::
|
||||||
|
|
||||||
|
static int
|
||||||
|
SubList_init(SubListObject *self, PyObject *args, PyObject *kwds)
|
||||||
|
{
|
||||||
|
if (PyList_Type.tp_init((PyObject *) self, args, kwds) < 0)
|
||||||
|
return -1;
|
||||||
|
self->state = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
We see above how to call through to the :attr:`__init__` method of the base
|
||||||
|
type.
|
||||||
|
|
||||||
|
This pattern is important when writing a type with custom
|
||||||
|
:c:member:`~PyTypeObject.tp_new` and :c:member:`~PyTypeObject.tp_dealloc`
|
||||||
|
members. The :c:member:`~PyTypeObject.tp_new` handler should not actually
|
||||||
|
create the memory for the object with its :c:member:`~PyTypeObject.tp_alloc`,
|
||||||
|
but let the base class handle it by calling its own :c:member:`~PyTypeObject.tp_new`.
|
||||||
|
|
||||||
|
The :c:type:`PyTypeObject` struct supports a :c:member:`~PyTypeObject.tp_base`
|
||||||
|
specifying the type's concrete base class. Due to cross-platform compiler
|
||||||
|
issues, you can't fill that field directly with a reference to
|
||||||
|
:c:type:`PyList_Type`; it should be done later in the module initialization
|
||||||
|
function::
|
||||||
|
|
||||||
|
PyMODINIT_FUNC
|
||||||
|
PyInit_sublist(void)
|
||||||
|
{
|
||||||
|
PyObject* m;
|
||||||
|
SubListType.tp_base = &PyList_Type;
|
||||||
|
if (PyType_Ready(&SubListType) < 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
m = PyModule_Create(&sublistmodule);
|
||||||
|
if (m == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
Py_INCREF(&SubListType);
|
||||||
|
PyModule_AddObject(m, "SubList", (PyObject *) &SubListType);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
Before calling :c:func:`PyType_Ready`, the type structure must have the
|
||||||
|
:c:member:`~PyTypeObject.tp_base` slot filled in. When we are deriving an
|
||||||
|
existing type, it is not necessary to fill out the :c:member:`~PyTypeObject.tp_alloc`
|
||||||
|
slot with :c:func:`PyType_GenericNew` -- the allocation function from the base
|
||||||
|
type will be inherited.
|
||||||
|
|
||||||
|
After that, calling :c:func:`PyType_Ready` and adding the type object to the
|
||||||
|
module is the same as with the basic :class:`Custom` examples.
|
||||||
|
|
||||||
|
|
||||||
|
.. rubric:: Footnotes
|
||||||
|
|
||||||
|
.. [#] This is true when we know that the object is a basic type, like a string or a
|
||||||
|
float.
|
||||||
|
|
||||||
|
.. [#] We relied on this in the :c:member:`~PyTypeObject.tp_dealloc` handler
|
||||||
|
in this example, because our type doesn't support garbage collection.
|
||||||
|
|
||||||
|
.. [#] We now know that the first and last members are strings, so perhaps we
|
||||||
|
could be less careful about decrementing their reference counts, however,
|
||||||
|
we accept instances of string subclasses. Even though deallocating normal
|
||||||
|
strings won't call back into our objects, we can't guarantee that deallocating
|
||||||
|
an instance of a string subclass won't call back into our objects.
|
||||||
|
|
||||||
|
.. [#] Also, even with our attributes restricted to strings instances, the user
|
||||||
|
could pass arbitrary :class:`str` subclasses and therefore still create
|
||||||
|
reference cycles.
|
|
@ -0,0 +1,39 @@
|
||||||
|
#include <Python.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
PyObject_HEAD
|
||||||
|
/* Type-specific fields go here. */
|
||||||
|
} CustomObject;
|
||||||
|
|
||||||
|
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,
|
||||||
|
.tp_new = PyType_GenericNew,
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
|
@ -0,0 +1,132 @@
|
||||||
|
#include <Python.h>
|
||||||
|
#include "structmember.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
PyObject_HEAD
|
||||||
|
PyObject *first; /* first name */
|
||||||
|
PyObject *last; /* last name */
|
||||||
|
int number;
|
||||||
|
} CustomObject;
|
||||||
|
|
||||||
|
static void
|
||||||
|
Custom_dealloc(CustomObject *self)
|
||||||
|
{
|
||||||
|
Py_XDECREF(self->first);
|
||||||
|
Py_XDECREF(self->last);
|
||||||
|
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, "|OOi", kwlist,
|
||||||
|
&first, &last,
|
||||||
|
&self->number))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (first) {
|
||||||
|
tmp = self->first;
|
||||||
|
Py_INCREF(first);
|
||||||
|
self->first = first;
|
||||||
|
Py_XDECREF(tmp);
|
||||||
|
}
|
||||||
|
if (last) {
|
||||||
|
tmp = self->last;
|
||||||
|
Py_INCREF(last);
|
||||||
|
self->last = last;
|
||||||
|
Py_XDECREF(tmp);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyMemberDef Custom_members[] = {
|
||||||
|
{"first", T_OBJECT_EX, offsetof(CustomObject, first), 0,
|
||||||
|
"first name"},
|
||||||
|
{"last", T_OBJECT_EX, offsetof(CustomObject, last), 0,
|
||||||
|
"last name"},
|
||||||
|
{"number", T_INT, offsetof(CustomObject, number), 0,
|
||||||
|
"custom number"},
|
||||||
|
{NULL} /* Sentinel */
|
||||||
|
};
|
||||||
|
|
||||||
|
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 */
|
||||||
|
};
|
||||||
|
|
||||||
|
static PyTypeObject CustomType = {
|
||||||
|
PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
|
.tp_name = "custom2.Custom",
|
||||||
|
.tp_doc = "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 = {
|
||||||
|
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;
|
||||||
|
|
||||||
|
Py_INCREF(&CustomType);
|
||||||
|
PyModule_AddObject(m, "Custom", (PyObject *) &CustomType);
|
||||||
|
return m;
|
||||||
|
}
|
|
@ -0,0 +1,183 @@
|
||||||
|
#include <Python.h>
|
||||||
|
#include "structmember.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
PyObject_HEAD
|
||||||
|
PyObject *first; /* first name */
|
||||||
|
PyObject *last; /* last name */
|
||||||
|
int number;
|
||||||
|
} CustomObject;
|
||||||
|
|
||||||
|
static void
|
||||||
|
Custom_dealloc(CustomObject *self)
|
||||||
|
{
|
||||||
|
Py_XDECREF(self->first);
|
||||||
|
Py_XDECREF(self->last);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
PyObject *tmp;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
tmp = self->first;
|
||||||
|
Py_INCREF(value);
|
||||||
|
self->first = value;
|
||||||
|
Py_DECREF(tmp);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
PyObject *tmp;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
tmp = self->last;
|
||||||
|
Py_INCREF(value);
|
||||||
|
self->last = value;
|
||||||
|
Py_DECREF(tmp);
|
||||||
|
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 = "custom3.Custom",
|
||||||
|
.tp_doc = "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,
|
||||||
|
.tp_getset = Custom_getsetters,
|
||||||
|
};
|
||||||
|
|
||||||
|
static PyModuleDef custommodule = {
|
||||||
|
PyModuleDef_HEAD_INIT,
|
||||||
|
.m_name = "custom3",
|
||||||
|
.m_doc = "Example module that creates an extension type.",
|
||||||
|
.m_size = -1,
|
||||||
|
};
|
||||||
|
|
||||||
|
PyMODINIT_FUNC
|
||||||
|
PyInit_custom3(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;
|
||||||
|
}
|
|
@ -0,0 +1,197 @@
|
||||||
|
#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 = "custom4.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 = "custom4",
|
||||||
|
.m_doc = "Example module that creates an extension type.",
|
||||||
|
.m_size = -1,
|
||||||
|
};
|
||||||
|
|
||||||
|
PyMODINIT_FUNC
|
||||||
|
PyInit_custom4(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;
|
||||||
|
}
|
|
@ -1,56 +0,0 @@
|
||||||
#include <Python.h>
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
PyObject_HEAD
|
|
||||||
/* Type-specific fields go here. */
|
|
||||||
} noddy_NoddyObject;
|
|
||||||
|
|
||||||
static PyTypeObject noddy_NoddyType = {
|
|
||||||
PyVarObject_HEAD_INIT(NULL, 0)
|
|
||||||
"noddy.Noddy", /* tp_name */
|
|
||||||
sizeof(noddy_NoddyObject), /* tp_basicsize */
|
|
||||||
0, /* tp_itemsize */
|
|
||||||
0, /* tp_dealloc */
|
|
||||||
0, /* tp_print */
|
|
||||||
0, /* tp_getattr */
|
|
||||||
0, /* tp_setattr */
|
|
||||||
0, /* tp_reserved */
|
|
||||||
0, /* tp_repr */
|
|
||||||
0, /* tp_as_number */
|
|
||||||
0, /* tp_as_sequence */
|
|
||||||
0, /* tp_as_mapping */
|
|
||||||
0, /* tp_hash */
|
|
||||||
0, /* tp_call */
|
|
||||||
0, /* tp_str */
|
|
||||||
0, /* tp_getattro */
|
|
||||||
0, /* tp_setattro */
|
|
||||||
0, /* tp_as_buffer */
|
|
||||||
Py_TPFLAGS_DEFAULT, /* tp_flags */
|
|
||||||
"Noddy objects", /* tp_doc */
|
|
||||||
};
|
|
||||||
|
|
||||||
static PyModuleDef noddymodule = {
|
|
||||||
PyModuleDef_HEAD_INIT,
|
|
||||||
"noddy",
|
|
||||||
"Example module that creates an extension type.",
|
|
||||||
-1,
|
|
||||||
NULL, NULL, NULL, NULL, NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
PyMODINIT_FUNC
|
|
||||||
PyInit_noddy(void)
|
|
||||||
{
|
|
||||||
PyObject* m;
|
|
||||||
|
|
||||||
noddy_NoddyType.tp_new = PyType_GenericNew;
|
|
||||||
if (PyType_Ready(&noddy_NoddyType) < 0)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
m = PyModule_Create(&noddymodule);
|
|
||||||
if (m == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
Py_INCREF(&noddy_NoddyType);
|
|
||||||
PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType);
|
|
||||||
return m;
|
|
||||||
}
|
|
|
@ -1,172 +0,0 @@
|
||||||
#include <Python.h>
|
|
||||||
#include "structmember.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
PyObject_HEAD
|
|
||||||
PyObject *first; /* first name */
|
|
||||||
PyObject *last; /* last name */
|
|
||||||
int number;
|
|
||||||
} Noddy;
|
|
||||||
|
|
||||||
static void
|
|
||||||
Noddy_dealloc(Noddy* self)
|
|
||||||
{
|
|
||||||
Py_XDECREF(self->first);
|
|
||||||
Py_XDECREF(self->last);
|
|
||||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
|
||||||
{
|
|
||||||
Noddy *self;
|
|
||||||
|
|
||||||
self = (Noddy *)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
|
|
||||||
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
|
|
||||||
{
|
|
||||||
PyObject *first=NULL, *last=NULL, *tmp;
|
|
||||||
|
|
||||||
static char *kwlist[] = {"first", "last", "number", NULL};
|
|
||||||
|
|
||||||
if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
|
|
||||||
&first, &last,
|
|
||||||
&self->number))
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (first) {
|
|
||||||
tmp = self->first;
|
|
||||||
Py_INCREF(first);
|
|
||||||
self->first = first;
|
|
||||||
Py_XDECREF(tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (last) {
|
|
||||||
tmp = self->last;
|
|
||||||
Py_INCREF(last);
|
|
||||||
self->last = last;
|
|
||||||
Py_XDECREF(tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static PyMemberDef Noddy_members[] = {
|
|
||||||
{"first", T_OBJECT_EX, offsetof(Noddy, first), 0,
|
|
||||||
"first name"},
|
|
||||||
{"last", T_OBJECT_EX, offsetof(Noddy, last), 0,
|
|
||||||
"last name"},
|
|
||||||
{"number", T_INT, offsetof(Noddy, number), 0,
|
|
||||||
"noddy number"},
|
|
||||||
{NULL} /* Sentinel */
|
|
||||||
};
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
Noddy_name(Noddy* self)
|
|
||||||
{
|
|
||||||
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 Noddy_methods[] = {
|
|
||||||
{"name", (PyCFunction)Noddy_name, METH_NOARGS,
|
|
||||||
"Return the name, combining the first and last name"
|
|
||||||
},
|
|
||||||
{NULL} /* Sentinel */
|
|
||||||
};
|
|
||||||
|
|
||||||
static PyTypeObject NoddyType = {
|
|
||||||
PyVarObject_HEAD_INIT(NULL, 0)
|
|
||||||
"noddy.Noddy", /* tp_name */
|
|
||||||
sizeof(Noddy), /* tp_basicsize */
|
|
||||||
0, /* tp_itemsize */
|
|
||||||
(destructor)Noddy_dealloc, /* tp_dealloc */
|
|
||||||
0, /* tp_print */
|
|
||||||
0, /* tp_getattr */
|
|
||||||
0, /* tp_setattr */
|
|
||||||
0, /* tp_reserved */
|
|
||||||
0, /* tp_repr */
|
|
||||||
0, /* tp_as_number */
|
|
||||||
0, /* tp_as_sequence */
|
|
||||||
0, /* tp_as_mapping */
|
|
||||||
0, /* tp_hash */
|
|
||||||
0, /* tp_call */
|
|
||||||
0, /* tp_str */
|
|
||||||
0, /* tp_getattro */
|
|
||||||
0, /* tp_setattro */
|
|
||||||
0, /* tp_as_buffer */
|
|
||||||
Py_TPFLAGS_DEFAULT |
|
|
||||||
Py_TPFLAGS_BASETYPE, /* tp_flags */
|
|
||||||
"Noddy objects", /* tp_doc */
|
|
||||||
0, /* tp_traverse */
|
|
||||||
0, /* tp_clear */
|
|
||||||
0, /* tp_richcompare */
|
|
||||||
0, /* tp_weaklistoffset */
|
|
||||||
0, /* tp_iter */
|
|
||||||
0, /* tp_iternext */
|
|
||||||
Noddy_methods, /* tp_methods */
|
|
||||||
Noddy_members, /* tp_members */
|
|
||||||
0, /* tp_getset */
|
|
||||||
0, /* tp_base */
|
|
||||||
0, /* tp_dict */
|
|
||||||
0, /* tp_descr_get */
|
|
||||||
0, /* tp_descr_set */
|
|
||||||
0, /* tp_dictoffset */
|
|
||||||
(initproc)Noddy_init, /* tp_init */
|
|
||||||
0, /* tp_alloc */
|
|
||||||
Noddy_new, /* tp_new */
|
|
||||||
};
|
|
||||||
|
|
||||||
static PyModuleDef noddy2module = {
|
|
||||||
PyModuleDef_HEAD_INIT,
|
|
||||||
"noddy2",
|
|
||||||
"Example module that creates an extension type.",
|
|
||||||
-1,
|
|
||||||
NULL, NULL, NULL, NULL, NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
PyMODINIT_FUNC
|
|
||||||
PyInit_noddy2(void)
|
|
||||||
{
|
|
||||||
PyObject* m;
|
|
||||||
|
|
||||||
if (PyType_Ready(&NoddyType) < 0)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
m = PyModule_Create(&noddy2module);
|
|
||||||
if (m == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
Py_INCREF(&NoddyType);
|
|
||||||
PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);
|
|
||||||
return m;
|
|
||||||
}
|
|
|
@ -1,225 +0,0 @@
|
||||||
#include <Python.h>
|
|
||||||
#include "structmember.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
PyObject_HEAD
|
|
||||||
PyObject *first;
|
|
||||||
PyObject *last;
|
|
||||||
int number;
|
|
||||||
} Noddy;
|
|
||||||
|
|
||||||
static void
|
|
||||||
Noddy_dealloc(Noddy* self)
|
|
||||||
{
|
|
||||||
Py_XDECREF(self->first);
|
|
||||||
Py_XDECREF(self->last);
|
|
||||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
|
||||||
{
|
|
||||||
Noddy *self;
|
|
||||||
|
|
||||||
self = (Noddy *)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
|
|
||||||
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
|
|
||||||
{
|
|
||||||
PyObject *first=NULL, *last=NULL, *tmp;
|
|
||||||
|
|
||||||
static char *kwlist[] = {"first", "last", "number", NULL};
|
|
||||||
|
|
||||||
if (! PyArg_ParseTupleAndKeywords(args, kwds, "|SSi", 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 Noddy_members[] = {
|
|
||||||
{"number", T_INT, offsetof(Noddy, number), 0,
|
|
||||||
"noddy number"},
|
|
||||||
{NULL} /* Sentinel */
|
|
||||||
};
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
Noddy_getfirst(Noddy *self, void *closure)
|
|
||||||
{
|
|
||||||
Py_INCREF(self->first);
|
|
||||||
return self->first;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
Noddy_setfirst(Noddy *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_DECREF(self->first);
|
|
||||||
Py_INCREF(value);
|
|
||||||
self->first = value;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
Noddy_getlast(Noddy *self, void *closure)
|
|
||||||
{
|
|
||||||
Py_INCREF(self->last);
|
|
||||||
return self->last;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
Noddy_setlast(Noddy *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_DECREF(self->last);
|
|
||||||
Py_INCREF(value);
|
|
||||||
self->last = value;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyGetSetDef Noddy_getseters[] = {
|
|
||||||
{"first",
|
|
||||||
(getter)Noddy_getfirst, (setter)Noddy_setfirst,
|
|
||||||
"first name",
|
|
||||||
NULL},
|
|
||||||
{"last",
|
|
||||||
(getter)Noddy_getlast, (setter)Noddy_setlast,
|
|
||||||
"last name",
|
|
||||||
NULL},
|
|
||||||
{NULL} /* Sentinel */
|
|
||||||
};
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
Noddy_name(Noddy* self)
|
|
||||||
{
|
|
||||||
return PyUnicode_FromFormat("%S %S", self->first, self->last);
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyMethodDef Noddy_methods[] = {
|
|
||||||
{"name", (PyCFunction)Noddy_name, METH_NOARGS,
|
|
||||||
"Return the name, combining the first and last name"
|
|
||||||
},
|
|
||||||
{NULL} /* Sentinel */
|
|
||||||
};
|
|
||||||
|
|
||||||
static PyTypeObject NoddyType = {
|
|
||||||
PyVarObject_HEAD_INIT(NULL, 0)
|
|
||||||
"noddy.Noddy", /* tp_name */
|
|
||||||
sizeof(Noddy), /* tp_basicsize */
|
|
||||||
0, /* tp_itemsize */
|
|
||||||
(destructor)Noddy_dealloc, /* tp_dealloc */
|
|
||||||
0, /* tp_print */
|
|
||||||
0, /* tp_getattr */
|
|
||||||
0, /* tp_setattr */
|
|
||||||
0, /* tp_reserved */
|
|
||||||
0, /* tp_repr */
|
|
||||||
0, /* tp_as_number */
|
|
||||||
0, /* tp_as_sequence */
|
|
||||||
0, /* tp_as_mapping */
|
|
||||||
0, /* tp_hash */
|
|
||||||
0, /* tp_call */
|
|
||||||
0, /* tp_str */
|
|
||||||
0, /* tp_getattro */
|
|
||||||
0, /* tp_setattro */
|
|
||||||
0, /* tp_as_buffer */
|
|
||||||
Py_TPFLAGS_DEFAULT |
|
|
||||||
Py_TPFLAGS_BASETYPE, /* tp_flags */
|
|
||||||
"Noddy objects", /* tp_doc */
|
|
||||||
0, /* tp_traverse */
|
|
||||||
0, /* tp_clear */
|
|
||||||
0, /* tp_richcompare */
|
|
||||||
0, /* tp_weaklistoffset */
|
|
||||||
0, /* tp_iter */
|
|
||||||
0, /* tp_iternext */
|
|
||||||
Noddy_methods, /* tp_methods */
|
|
||||||
Noddy_members, /* tp_members */
|
|
||||||
Noddy_getseters, /* tp_getset */
|
|
||||||
0, /* tp_base */
|
|
||||||
0, /* tp_dict */
|
|
||||||
0, /* tp_descr_get */
|
|
||||||
0, /* tp_descr_set */
|
|
||||||
0, /* tp_dictoffset */
|
|
||||||
(initproc)Noddy_init, /* tp_init */
|
|
||||||
0, /* tp_alloc */
|
|
||||||
Noddy_new, /* tp_new */
|
|
||||||
};
|
|
||||||
|
|
||||||
static PyModuleDef noddy3module = {
|
|
||||||
PyModuleDef_HEAD_INIT,
|
|
||||||
"noddy3",
|
|
||||||
"Example module that creates an extension type.",
|
|
||||||
-1,
|
|
||||||
NULL, NULL, NULL, NULL, NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
PyMODINIT_FUNC
|
|
||||||
PyInit_noddy3(void)
|
|
||||||
{
|
|
||||||
PyObject* m;
|
|
||||||
|
|
||||||
if (PyType_Ready(&NoddyType) < 0)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
m = PyModule_Create(&noddy3module);
|
|
||||||
if (m == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
Py_INCREF(&NoddyType);
|
|
||||||
PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);
|
|
||||||
return m;
|
|
||||||
}
|
|
|
@ -1,208 +0,0 @@
|
||||||
#include <Python.h>
|
|
||||||
#include "structmember.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
PyObject_HEAD
|
|
||||||
PyObject *first;
|
|
||||||
PyObject *last;
|
|
||||||
int number;
|
|
||||||
} Noddy;
|
|
||||||
|
|
||||||
static int
|
|
||||||
Noddy_traverse(Noddy *self, visitproc visit, void *arg)
|
|
||||||
{
|
|
||||||
int vret;
|
|
||||||
|
|
||||||
if (self->first) {
|
|
||||||
vret = visit(self->first, arg);
|
|
||||||
if (vret != 0)
|
|
||||||
return vret;
|
|
||||||
}
|
|
||||||
if (self->last) {
|
|
||||||
vret = visit(self->last, arg);
|
|
||||||
if (vret != 0)
|
|
||||||
return vret;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
Noddy_clear(Noddy *self)
|
|
||||||
{
|
|
||||||
PyObject *tmp;
|
|
||||||
|
|
||||||
tmp = self->first;
|
|
||||||
self->first = NULL;
|
|
||||||
Py_XDECREF(tmp);
|
|
||||||
|
|
||||||
tmp = self->last;
|
|
||||||
self->last = NULL;
|
|
||||||
Py_XDECREF(tmp);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
Noddy_dealloc(Noddy* self)
|
|
||||||
{
|
|
||||||
PyObject_GC_UnTrack(self);
|
|
||||||
Noddy_clear(self);
|
|
||||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
|
||||||
{
|
|
||||||
Noddy *self;
|
|
||||||
|
|
||||||
self = (Noddy *)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
|
|
||||||
Noddy_init(Noddy *self, PyObject *args, PyObject *kwds)
|
|
||||||
{
|
|
||||||
PyObject *first=NULL, *last=NULL, *tmp;
|
|
||||||
|
|
||||||
static char *kwlist[] = {"first", "last", "number", NULL};
|
|
||||||
|
|
||||||
if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
|
|
||||||
&first, &last,
|
|
||||||
&self->number))
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (first) {
|
|
||||||
tmp = self->first;
|
|
||||||
Py_INCREF(first);
|
|
||||||
self->first = first;
|
|
||||||
Py_XDECREF(tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (last) {
|
|
||||||
tmp = self->last;
|
|
||||||
Py_INCREF(last);
|
|
||||||
self->last = last;
|
|
||||||
Py_XDECREF(tmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static PyMemberDef Noddy_members[] = {
|
|
||||||
{"first", T_OBJECT_EX, offsetof(Noddy, first), 0,
|
|
||||||
"first name"},
|
|
||||||
{"last", T_OBJECT_EX, offsetof(Noddy, last), 0,
|
|
||||||
"last name"},
|
|
||||||
{"number", T_INT, offsetof(Noddy, number), 0,
|
|
||||||
"noddy number"},
|
|
||||||
{NULL} /* Sentinel */
|
|
||||||
};
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
Noddy_name(Noddy* self)
|
|
||||||
{
|
|
||||||
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 Noddy_methods[] = {
|
|
||||||
{"name", (PyCFunction)Noddy_name, METH_NOARGS,
|
|
||||||
"Return the name, combining the first and last name"
|
|
||||||
},
|
|
||||||
{NULL} /* Sentinel */
|
|
||||||
};
|
|
||||||
|
|
||||||
static PyTypeObject NoddyType = {
|
|
||||||
PyVarObject_HEAD_INIT(NULL, 0)
|
|
||||||
"noddy.Noddy", /* tp_name */
|
|
||||||
sizeof(Noddy), /* tp_basicsize */
|
|
||||||
0, /* tp_itemsize */
|
|
||||||
(destructor)Noddy_dealloc, /* tp_dealloc */
|
|
||||||
0, /* tp_print */
|
|
||||||
0, /* tp_getattr */
|
|
||||||
0, /* tp_setattr */
|
|
||||||
0, /* tp_reserved */
|
|
||||||
0, /* tp_repr */
|
|
||||||
0, /* tp_as_number */
|
|
||||||
0, /* tp_as_sequence */
|
|
||||||
0, /* tp_as_mapping */
|
|
||||||
0, /* tp_hash */
|
|
||||||
0, /* tp_call */
|
|
||||||
0, /* tp_str */
|
|
||||||
0, /* tp_getattro */
|
|
||||||
0, /* tp_setattro */
|
|
||||||
0, /* tp_as_buffer */
|
|
||||||
Py_TPFLAGS_DEFAULT |
|
|
||||||
Py_TPFLAGS_BASETYPE |
|
|
||||||
Py_TPFLAGS_HAVE_GC, /* tp_flags */
|
|
||||||
"Noddy objects", /* tp_doc */
|
|
||||||
(traverseproc)Noddy_traverse, /* tp_traverse */
|
|
||||||
(inquiry)Noddy_clear, /* tp_clear */
|
|
||||||
0, /* tp_richcompare */
|
|
||||||
0, /* tp_weaklistoffset */
|
|
||||||
0, /* tp_iter */
|
|
||||||
0, /* tp_iternext */
|
|
||||||
Noddy_methods, /* tp_methods */
|
|
||||||
Noddy_members, /* tp_members */
|
|
||||||
0, /* tp_getset */
|
|
||||||
0, /* tp_base */
|
|
||||||
0, /* tp_dict */
|
|
||||||
0, /* tp_descr_get */
|
|
||||||
0, /* tp_descr_set */
|
|
||||||
0, /* tp_dictoffset */
|
|
||||||
(initproc)Noddy_init, /* tp_init */
|
|
||||||
0, /* tp_alloc */
|
|
||||||
Noddy_new, /* tp_new */
|
|
||||||
};
|
|
||||||
|
|
||||||
static PyModuleDef noddy4module = {
|
|
||||||
PyModuleDef_HEAD_INIT,
|
|
||||||
"noddy4",
|
|
||||||
"Example module that creates an extension type.",
|
|
||||||
-1,
|
|
||||||
NULL, NULL, NULL, NULL, NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
PyMODINIT_FUNC
|
|
||||||
PyInit_noddy4(void)
|
|
||||||
{
|
|
||||||
PyObject* m;
|
|
||||||
|
|
||||||
if (PyType_Ready(&NoddyType) < 0)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
m = PyModule_Create(&noddy4module);
|
|
||||||
if (m == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
Py_INCREF(&NoddyType);
|
|
||||||
PyModule_AddObject(m, "Noddy", (PyObject *)&NoddyType);
|
|
||||||
return m;
|
|
||||||
}
|
|
|
@ -1,99 +0,0 @@
|
||||||
#include <Python.h>
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
PyListObject list;
|
|
||||||
int state;
|
|
||||||
} Shoddy;
|
|
||||||
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
Shoddy_increment(Shoddy *self, PyObject *unused)
|
|
||||||
{
|
|
||||||
self->state++;
|
|
||||||
return PyLong_FromLong(self->state);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static PyMethodDef Shoddy_methods[] = {
|
|
||||||
{"increment", (PyCFunction)Shoddy_increment, METH_NOARGS,
|
|
||||||
PyDoc_STR("increment state counter")},
|
|
||||||
{NULL, NULL},
|
|
||||||
};
|
|
||||||
|
|
||||||
static int
|
|
||||||
Shoddy_init(Shoddy *self, PyObject *args, PyObject *kwds)
|
|
||||||
{
|
|
||||||
if (PyList_Type.tp_init((PyObject *)self, args, kwds) < 0)
|
|
||||||
return -1;
|
|
||||||
self->state = 0;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static PyTypeObject ShoddyType = {
|
|
||||||
PyVarObject_HEAD_INIT(NULL, 0)
|
|
||||||
"shoddy.Shoddy", /* tp_name */
|
|
||||||
sizeof(Shoddy), /* tp_basicsize */
|
|
||||||
0, /* tp_itemsize */
|
|
||||||
0, /* tp_dealloc */
|
|
||||||
0, /* tp_print */
|
|
||||||
0, /* tp_getattr */
|
|
||||||
0, /* tp_setattr */
|
|
||||||
0, /* tp_reserved */
|
|
||||||
0, /* tp_repr */
|
|
||||||
0, /* tp_as_number */
|
|
||||||
0, /* tp_as_sequence */
|
|
||||||
0, /* tp_as_mapping */
|
|
||||||
0, /* tp_hash */
|
|
||||||
0, /* tp_call */
|
|
||||||
0, /* tp_str */
|
|
||||||
0, /* tp_getattro */
|
|
||||||
0, /* tp_setattro */
|
|
||||||
0, /* tp_as_buffer */
|
|
||||||
Py_TPFLAGS_DEFAULT |
|
|
||||||
Py_TPFLAGS_BASETYPE, /* tp_flags */
|
|
||||||
0, /* tp_doc */
|
|
||||||
0, /* tp_traverse */
|
|
||||||
0, /* tp_clear */
|
|
||||||
0, /* tp_richcompare */
|
|
||||||
0, /* tp_weaklistoffset */
|
|
||||||
0, /* tp_iter */
|
|
||||||
0, /* tp_iternext */
|
|
||||||
Shoddy_methods, /* tp_methods */
|
|
||||||
0, /* tp_members */
|
|
||||||
0, /* tp_getset */
|
|
||||||
0, /* tp_base */
|
|
||||||
0, /* tp_dict */
|
|
||||||
0, /* tp_descr_get */
|
|
||||||
0, /* tp_descr_set */
|
|
||||||
0, /* tp_dictoffset */
|
|
||||||
(initproc)Shoddy_init, /* tp_init */
|
|
||||||
0, /* tp_alloc */
|
|
||||||
0, /* tp_new */
|
|
||||||
};
|
|
||||||
|
|
||||||
static PyModuleDef shoddymodule = {
|
|
||||||
PyModuleDef_HEAD_INIT,
|
|
||||||
"shoddy",
|
|
||||||
"Shoddy module",
|
|
||||||
-1,
|
|
||||||
NULL, NULL, NULL, NULL, NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
PyMODINIT_FUNC
|
|
||||||
PyInit_shoddy(void)
|
|
||||||
{
|
|
||||||
PyObject *m;
|
|
||||||
|
|
||||||
ShoddyType.tp_base = &PyList_Type;
|
|
||||||
if (PyType_Ready(&ShoddyType) < 0)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
m = PyModule_Create(&shoddymodule);
|
|
||||||
if (m == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
Py_INCREF(&ShoddyType);
|
|
||||||
PyModule_AddObject(m, "Shoddy", (PyObject *) &ShoddyType);
|
|
||||||
return m;
|
|
||||||
}
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
#include <Python.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
PyListObject list;
|
||||||
|
int state;
|
||||||
|
} SubListObject;
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
SubList_increment(SubListObject *self, PyObject *unused)
|
||||||
|
{
|
||||||
|
self->state++;
|
||||||
|
return PyLong_FromLong(self->state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyMethodDef SubList_methods[] = {
|
||||||
|
{"increment", (PyCFunction) SubList_increment, METH_NOARGS,
|
||||||
|
PyDoc_STR("increment state counter")},
|
||||||
|
{NULL},
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
SubList_init(SubListObject *self, PyObject *args, PyObject *kwds)
|
||||||
|
{
|
||||||
|
if (PyList_Type.tp_init((PyObject *) self, args, kwds) < 0)
|
||||||
|
return -1;
|
||||||
|
self->state = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyTypeObject SubListType = {
|
||||||
|
PyVarObject_HEAD_INIT(NULL, 0)
|
||||||
|
.tp_name = "sublist.SubList",
|
||||||
|
.tp_doc = "SubList objects",
|
||||||
|
.tp_basicsize = sizeof(SubListObject),
|
||||||
|
.tp_itemsize = 0,
|
||||||
|
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
|
||||||
|
.tp_init = (initproc) SubList_init,
|
||||||
|
.tp_methods = SubList_methods,
|
||||||
|
};
|
||||||
|
|
||||||
|
static PyModuleDef sublistmodule = {
|
||||||
|
PyModuleDef_HEAD_INIT,
|
||||||
|
.m_name = "sublist",
|
||||||
|
.m_doc = "Example module that creates an extension type.",
|
||||||
|
.m_size = -1,
|
||||||
|
};
|
||||||
|
|
||||||
|
PyMODINIT_FUNC
|
||||||
|
PyInit_sublist(void)
|
||||||
|
{
|
||||||
|
PyObject *m;
|
||||||
|
SubListType.tp_base = &PyList_Type;
|
||||||
|
if (PyType_Ready(&SubListType) < 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
m = PyModule_Create(&sublistmodule);
|
||||||
|
if (m == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
Py_INCREF(&SubListType);
|
||||||
|
PyModule_AddObject(m, "SubList", (PyObject *) &SubListType);
|
||||||
|
return m;
|
||||||
|
}
|
|
@ -1,181 +1,168 @@
|
||||||
"""Test module for the noddy examples
|
"""Test module for the custom examples
|
||||||
|
|
||||||
Noddy 1:
|
Custom 1:
|
||||||
|
|
||||||
>>> import noddy
|
>>> import custom
|
||||||
>>> n1 = noddy.Noddy()
|
>>> c1 = custom.Custom()
|
||||||
>>> n2 = noddy.Noddy()
|
>>> c2 = custom.Custom()
|
||||||
>>> del n1
|
>>> del c1
|
||||||
>>> del n2
|
>>> del c2
|
||||||
|
|
||||||
|
|
||||||
Noddy 2
|
Custom 2
|
||||||
|
|
||||||
>>> import noddy2
|
>>> import custom2
|
||||||
>>> n1 = noddy2.Noddy('jim', 'fulton', 42)
|
>>> c1 = custom2.Custom('jim', 'fulton', 42)
|
||||||
>>> n1.first
|
>>> c1.first
|
||||||
'jim'
|
'jim'
|
||||||
>>> n1.last
|
>>> c1.last
|
||||||
'fulton'
|
'fulton'
|
||||||
>>> n1.number
|
>>> c1.number
|
||||||
42
|
42
|
||||||
>>> n1.name()
|
>>> c1.name()
|
||||||
'jim fulton'
|
'jim fulton'
|
||||||
>>> n1.first = 'will'
|
>>> c1.first = 'will'
|
||||||
>>> n1.name()
|
>>> c1.name()
|
||||||
'will fulton'
|
'will fulton'
|
||||||
>>> n1.last = 'tell'
|
>>> c1.last = 'tell'
|
||||||
>>> n1.name()
|
>>> c1.name()
|
||||||
'will tell'
|
'will tell'
|
||||||
>>> del n1.first
|
>>> del c1.first
|
||||||
>>> n1.name()
|
>>> c1.name()
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
AttributeError: first
|
AttributeError: first
|
||||||
>>> n1.first
|
>>> c1.first
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
AttributeError: first
|
AttributeError: first
|
||||||
>>> n1.first = 'drew'
|
>>> c1.first = 'drew'
|
||||||
>>> n1.first
|
>>> c1.first
|
||||||
'drew'
|
'drew'
|
||||||
>>> del n1.number
|
>>> del c1.number
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
TypeError: can't delete numeric/char attribute
|
TypeError: can't delete numeric/char attribute
|
||||||
>>> n1.number=2
|
>>> c1.number=2
|
||||||
>>> n1.number
|
>>> c1.number
|
||||||
2
|
2
|
||||||
>>> n1.first = 42
|
>>> c1.first = 42
|
||||||
>>> n1.name()
|
>>> c1.name()
|
||||||
'42 tell'
|
'42 tell'
|
||||||
>>> n2 = noddy2.Noddy()
|
>>> c2 = custom2.Custom()
|
||||||
>>> n2.name()
|
>>> c2.name()
|
||||||
' '
|
' '
|
||||||
>>> n2.first
|
>>> c2.first
|
||||||
''
|
''
|
||||||
>>> n2.last
|
>>> c2.last
|
||||||
''
|
''
|
||||||
>>> del n2.first
|
>>> del c2.first
|
||||||
>>> n2.first
|
>>> c2.first
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
AttributeError: first
|
AttributeError: first
|
||||||
>>> n2.first
|
>>> c2.first
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
AttributeError: first
|
AttributeError: first
|
||||||
>>> n2.name()
|
>>> c2.name()
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
File "<stdin>", line 1, in ?
|
File "<stdin>", line 1, in ?
|
||||||
AttributeError: first
|
AttributeError: first
|
||||||
>>> n2.number
|
>>> c2.number
|
||||||
0
|
0
|
||||||
>>> n3 = noddy2.Noddy('jim', 'fulton', 'waaa')
|
>>> n3 = custom2.Custom('jim', 'fulton', 'waaa')
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
File "<stdin>", line 1, in ?
|
File "<stdin>", line 1, in ?
|
||||||
TypeError: an integer is required
|
TypeError: an integer is required (got type str)
|
||||||
>>> del n1
|
>>> del c1
|
||||||
>>> del n2
|
>>> del c2
|
||||||
|
|
||||||
|
|
||||||
Noddy 3
|
Custom 3
|
||||||
|
|
||||||
>>> import noddy3
|
>>> import custom3
|
||||||
>>> n1 = noddy3.Noddy('jim', 'fulton', 42)
|
>>> c1 = custom3.Custom('jim', 'fulton', 42)
|
||||||
>>> n1 = noddy3.Noddy('jim', 'fulton', 42)
|
>>> c1 = custom3.Custom('jim', 'fulton', 42)
|
||||||
>>> n1.name()
|
>>> c1.name()
|
||||||
'jim fulton'
|
'jim fulton'
|
||||||
>>> del n1.first
|
>>> del c1.first
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
File "<stdin>", line 1, in ?
|
File "<stdin>", line 1, in ?
|
||||||
TypeError: Cannot delete the first attribute
|
TypeError: Cannot delete the first attribute
|
||||||
>>> n1.first = 42
|
>>> c1.first = 42
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
File "<stdin>", line 1, in ?
|
File "<stdin>", line 1, in ?
|
||||||
TypeError: The first attribute value must be a string
|
TypeError: The first attribute value must be a string
|
||||||
>>> n1.first = 'will'
|
>>> c1.first = 'will'
|
||||||
>>> n1.name()
|
>>> c1.name()
|
||||||
'will fulton'
|
'will fulton'
|
||||||
>>> n2 = noddy3.Noddy()
|
>>> c2 = custom3.Custom()
|
||||||
>>> n2 = noddy3.Noddy()
|
>>> c2 = custom3.Custom()
|
||||||
>>> n2 = noddy3.Noddy()
|
>>> c2 = custom3.Custom()
|
||||||
>>> n3 = noddy3.Noddy('jim', 'fulton', 'waaa')
|
>>> n3 = custom3.Custom('jim', 'fulton', 'waaa')
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
File "<stdin>", line 1, in ?
|
File "<stdin>", line 1, in ?
|
||||||
TypeError: an integer is required
|
TypeError: an integer is required (got type str)
|
||||||
>>> del n1
|
>>> del c1
|
||||||
>>> del n2
|
>>> del c2
|
||||||
|
|
||||||
Noddy 4
|
Custom 4
|
||||||
|
|
||||||
>>> import noddy4
|
>>> import custom4
|
||||||
>>> n1 = noddy4.Noddy('jim', 'fulton', 42)
|
>>> c1 = custom4.Custom('jim', 'fulton', 42)
|
||||||
>>> n1.first
|
>>> c1.first
|
||||||
'jim'
|
'jim'
|
||||||
>>> n1.last
|
>>> c1.last
|
||||||
'fulton'
|
'fulton'
|
||||||
>>> n1.number
|
>>> c1.number
|
||||||
42
|
42
|
||||||
>>> n1.name()
|
>>> c1.name()
|
||||||
'jim fulton'
|
'jim fulton'
|
||||||
>>> n1.first = 'will'
|
>>> c1.first = 'will'
|
||||||
>>> n1.name()
|
>>> c1.name()
|
||||||
'will fulton'
|
'will fulton'
|
||||||
>>> n1.last = 'tell'
|
>>> c1.last = 'tell'
|
||||||
>>> n1.name()
|
>>> c1.name()
|
||||||
'will tell'
|
'will tell'
|
||||||
>>> del n1.first
|
>>> del c1.first
|
||||||
>>> n1.name()
|
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
AttributeError: first
|
TypeError: Cannot delete the first attribute
|
||||||
>>> n1.first
|
>>> c1.name()
|
||||||
Traceback (most recent call last):
|
'will tell'
|
||||||
...
|
>>> c1.first = 'drew'
|
||||||
AttributeError: first
|
>>> c1.first
|
||||||
>>> n1.first = 'drew'
|
|
||||||
>>> n1.first
|
|
||||||
'drew'
|
'drew'
|
||||||
>>> del n1.number
|
>>> del c1.number
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
TypeError: can't delete numeric/char attribute
|
TypeError: can't delete numeric/char attribute
|
||||||
>>> n1.number=2
|
>>> c1.number=2
|
||||||
>>> n1.number
|
>>> c1.number
|
||||||
2
|
2
|
||||||
>>> n1.first = 42
|
>>> c1.first = 42
|
||||||
>>> n1.name()
|
Traceback (most recent call last):
|
||||||
'42 tell'
|
...
|
||||||
>>> n2 = noddy4.Noddy()
|
TypeError: The first attribute value must be a string
|
||||||
>>> n2 = noddy4.Noddy()
|
>>> c1.name()
|
||||||
>>> n2 = noddy4.Noddy()
|
'drew tell'
|
||||||
>>> n2 = noddy4.Noddy()
|
>>> c2 = custom4.Custom()
|
||||||
>>> n2.name()
|
>>> c2 = custom4.Custom()
|
||||||
|
>>> c2 = custom4.Custom()
|
||||||
|
>>> c2 = custom4.Custom()
|
||||||
|
>>> c2.name()
|
||||||
' '
|
' '
|
||||||
>>> n2.first
|
>>> c2.first
|
||||||
''
|
''
|
||||||
>>> n2.last
|
>>> c2.last
|
||||||
''
|
''
|
||||||
>>> del n2.first
|
>>> c2.number
|
||||||
>>> n2.first
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
AttributeError: first
|
|
||||||
>>> n2.first
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
AttributeError: first
|
|
||||||
>>> n2.name()
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "<stdin>", line 1, in ?
|
|
||||||
AttributeError: first
|
|
||||||
>>> n2.number
|
|
||||||
0
|
0
|
||||||
>>> n3 = noddy4.Noddy('jim', 'fulton', 'waaa')
|
>>> n3 = custom4.Custom('jim', 'fulton', 'waaa')
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
File "<stdin>", line 1, in ?
|
...
|
||||||
TypeError: an integer is required
|
TypeError: an integer is required (got type str)
|
||||||
|
|
||||||
|
|
||||||
Test cyclic gc(?)
|
Test cyclic gc(?)
|
||||||
|
@ -183,15 +170,14 @@ Test cyclic gc(?)
|
||||||
>>> import gc
|
>>> import gc
|
||||||
>>> gc.disable()
|
>>> gc.disable()
|
||||||
|
|
||||||
>>> x = []
|
>>> class Subclass(custom4.Custom): pass
|
||||||
>>> l = [x]
|
...
|
||||||
>>> n2.first = l
|
>>> s = Subclass()
|
||||||
>>> n2.first
|
>>> s.cycle = [s]
|
||||||
[[]]
|
>>> s.cycle.append(s.cycle)
|
||||||
>>> l.append(n2)
|
>>> x = object()
|
||||||
>>> del l
|
>>> s.x = x
|
||||||
>>> del n1
|
>>> del s
|
||||||
>>> del n2
|
|
||||||
>>> sys.getrefcount(x)
|
>>> sys.getrefcount(x)
|
||||||
3
|
3
|
||||||
>>> ignore = gc.collect()
|
>>> ignore = gc.collect()
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Modernize documentation for writing C extension types.
|
Loading…
Reference in New Issue