bpo-33201: Modernize "Extension types" doc (GH-6337) (GH-6411)
* 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
)
Co-authored-by: Antoine Pitrou <pitrou@free.fr>
This commit is contained in:
parent
12d1dcd1f8
commit
31f1b52f1f
|
@ -26,9 +26,11 @@ Recommended third party tools
|
|||
=============================
|
||||
|
||||
This guide only covers the basic tools for creating extensions provided
|
||||
as part of this version of CPython. Third party tools like Cython,
|
||||
``cffi``, SWIG and Numba offer both simpler and more sophisticated
|
||||
approaches to creating C and C++ extensions for Python.
|
||||
as part of this version of CPython. Third party tools like
|
||||
`Cython <http://cython.org/>`_, `cffi <https://cffi.readthedocs.io>`_,
|
||||
`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::
|
||||
|
||||
|
@ -52,6 +54,7 @@ C extensions.
|
|||
:numbered:
|
||||
|
||||
extending.rst
|
||||
newtypes_tutorial.rst
|
||||
newtypes.rst
|
||||
building.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,72 +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 */
|
||||
0, /* tp_traverse */
|
||||
0, /* tp_clear */
|
||||
0, /* tp_richcompare */
|
||||
0, /* tp_weaklistoffset */
|
||||
0, /* tp_iter */
|
||||
0, /* tp_iternext */
|
||||
0, /* 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 */
|
||||
0, /* tp_init */
|
||||
0, /* tp_alloc */
|
||||
PyType_GenericNew, /* tp_new */
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
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},
|
||||
};
|
||||
|
||||
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
|
||||
>>> n1 = noddy.Noddy()
|
||||
>>> n2 = noddy.Noddy()
|
||||
>>> del n1
|
||||
>>> del n2
|
||||
>>> import custom
|
||||
>>> c1 = custom.Custom()
|
||||
>>> c2 = custom.Custom()
|
||||
>>> del c1
|
||||
>>> del c2
|
||||
|
||||
|
||||
Noddy 2
|
||||
Custom 2
|
||||
|
||||
>>> import noddy2
|
||||
>>> n1 = noddy2.Noddy('jim', 'fulton', 42)
|
||||
>>> n1.first
|
||||
>>> import custom2
|
||||
>>> c1 = custom2.Custom('jim', 'fulton', 42)
|
||||
>>> c1.first
|
||||
'jim'
|
||||
>>> n1.last
|
||||
>>> c1.last
|
||||
'fulton'
|
||||
>>> n1.number
|
||||
>>> c1.number
|
||||
42
|
||||
>>> n1.name()
|
||||
>>> c1.name()
|
||||
'jim fulton'
|
||||
>>> n1.first = 'will'
|
||||
>>> n1.name()
|
||||
>>> c1.first = 'will'
|
||||
>>> c1.name()
|
||||
'will fulton'
|
||||
>>> n1.last = 'tell'
|
||||
>>> n1.name()
|
||||
>>> c1.last = 'tell'
|
||||
>>> c1.name()
|
||||
'will tell'
|
||||
>>> del n1.first
|
||||
>>> n1.name()
|
||||
>>> del c1.first
|
||||
>>> c1.name()
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: first
|
||||
>>> n1.first
|
||||
>>> c1.first
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: first
|
||||
>>> n1.first = 'drew'
|
||||
>>> n1.first
|
||||
>>> c1.first = 'drew'
|
||||
>>> c1.first
|
||||
'drew'
|
||||
>>> del n1.number
|
||||
>>> del c1.number
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: can't delete numeric/char attribute
|
||||
>>> n1.number=2
|
||||
>>> n1.number
|
||||
>>> c1.number=2
|
||||
>>> c1.number
|
||||
2
|
||||
>>> n1.first = 42
|
||||
>>> n1.name()
|
||||
>>> c1.first = 42
|
||||
>>> c1.name()
|
||||
'42 tell'
|
||||
>>> n2 = noddy2.Noddy()
|
||||
>>> n2.name()
|
||||
>>> c2 = custom2.Custom()
|
||||
>>> c2.name()
|
||||
' '
|
||||
>>> n2.first
|
||||
>>> c2.first
|
||||
''
|
||||
>>> n2.last
|
||||
>>> c2.last
|
||||
''
|
||||
>>> del n2.first
|
||||
>>> n2.first
|
||||
>>> del c2.first
|
||||
>>> c2.first
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: first
|
||||
>>> n2.first
|
||||
>>> c2.first
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: first
|
||||
>>> n2.name()
|
||||
>>> c2.name()
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in ?
|
||||
AttributeError: first
|
||||
>>> n2.number
|
||||
>>> c2.number
|
||||
0
|
||||
>>> n3 = noddy2.Noddy('jim', 'fulton', 'waaa')
|
||||
>>> n3 = custom2.Custom('jim', 'fulton', 'waaa')
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in ?
|
||||
TypeError: an integer is required
|
||||
>>> del n1
|
||||
>>> del n2
|
||||
TypeError: an integer is required (got type str)
|
||||
>>> del c1
|
||||
>>> del c2
|
||||
|
||||
|
||||
Noddy 3
|
||||
Custom 3
|
||||
|
||||
>>> import noddy3
|
||||
>>> n1 = noddy3.Noddy('jim', 'fulton', 42)
|
||||
>>> n1 = noddy3.Noddy('jim', 'fulton', 42)
|
||||
>>> n1.name()
|
||||
>>> import custom3
|
||||
>>> c1 = custom3.Custom('jim', 'fulton', 42)
|
||||
>>> c1 = custom3.Custom('jim', 'fulton', 42)
|
||||
>>> c1.name()
|
||||
'jim fulton'
|
||||
>>> del n1.first
|
||||
>>> del c1.first
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in ?
|
||||
TypeError: Cannot delete the first attribute
|
||||
>>> n1.first = 42
|
||||
>>> c1.first = 42
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in ?
|
||||
TypeError: The first attribute value must be a string
|
||||
>>> n1.first = 'will'
|
||||
>>> n1.name()
|
||||
>>> c1.first = 'will'
|
||||
>>> c1.name()
|
||||
'will fulton'
|
||||
>>> n2 = noddy3.Noddy()
|
||||
>>> n2 = noddy3.Noddy()
|
||||
>>> n2 = noddy3.Noddy()
|
||||
>>> n3 = noddy3.Noddy('jim', 'fulton', 'waaa')
|
||||
>>> c2 = custom3.Custom()
|
||||
>>> c2 = custom3.Custom()
|
||||
>>> c2 = custom3.Custom()
|
||||
>>> n3 = custom3.Custom('jim', 'fulton', 'waaa')
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in ?
|
||||
TypeError: an integer is required
|
||||
>>> del n1
|
||||
>>> del n2
|
||||
TypeError: an integer is required (got type str)
|
||||
>>> del c1
|
||||
>>> del c2
|
||||
|
||||
Noddy 4
|
||||
Custom 4
|
||||
|
||||
>>> import noddy4
|
||||
>>> n1 = noddy4.Noddy('jim', 'fulton', 42)
|
||||
>>> n1.first
|
||||
>>> import custom4
|
||||
>>> c1 = custom4.Custom('jim', 'fulton', 42)
|
||||
>>> c1.first
|
||||
'jim'
|
||||
>>> n1.last
|
||||
>>> c1.last
|
||||
'fulton'
|
||||
>>> n1.number
|
||||
>>> c1.number
|
||||
42
|
||||
>>> n1.name()
|
||||
>>> c1.name()
|
||||
'jim fulton'
|
||||
>>> n1.first = 'will'
|
||||
>>> n1.name()
|
||||
>>> c1.first = 'will'
|
||||
>>> c1.name()
|
||||
'will fulton'
|
||||
>>> n1.last = 'tell'
|
||||
>>> n1.name()
|
||||
>>> c1.last = 'tell'
|
||||
>>> c1.name()
|
||||
'will tell'
|
||||
>>> del n1.first
|
||||
>>> n1.name()
|
||||
>>> del c1.first
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: first
|
||||
>>> n1.first
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
AttributeError: first
|
||||
>>> n1.first = 'drew'
|
||||
>>> n1.first
|
||||
TypeError: Cannot delete the first attribute
|
||||
>>> c1.name()
|
||||
'will tell'
|
||||
>>> c1.first = 'drew'
|
||||
>>> c1.first
|
||||
'drew'
|
||||
>>> del n1.number
|
||||
>>> del c1.number
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: can't delete numeric/char attribute
|
||||
>>> n1.number=2
|
||||
>>> n1.number
|
||||
>>> c1.number=2
|
||||
>>> c1.number
|
||||
2
|
||||
>>> n1.first = 42
|
||||
>>> n1.name()
|
||||
'42 tell'
|
||||
>>> n2 = noddy4.Noddy()
|
||||
>>> n2 = noddy4.Noddy()
|
||||
>>> n2 = noddy4.Noddy()
|
||||
>>> n2 = noddy4.Noddy()
|
||||
>>> n2.name()
|
||||
>>> c1.first = 42
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: The first attribute value must be a string
|
||||
>>> c1.name()
|
||||
'drew tell'
|
||||
>>> c2 = custom4.Custom()
|
||||
>>> c2 = custom4.Custom()
|
||||
>>> c2 = custom4.Custom()
|
||||
>>> c2 = custom4.Custom()
|
||||
>>> c2.name()
|
||||
' '
|
||||
>>> n2.first
|
||||
>>> c2.first
|
||||
''
|
||||
>>> n2.last
|
||||
>>> c2.last
|
||||
''
|
||||
>>> del n2.first
|
||||
>>> 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
|
||||
>>> c2.number
|
||||
0
|
||||
>>> n3 = noddy4.Noddy('jim', 'fulton', 'waaa')
|
||||
>>> n3 = custom4.Custom('jim', 'fulton', 'waaa')
|
||||
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(?)
|
||||
|
@ -183,15 +170,14 @@ Test cyclic gc(?)
|
|||
>>> import gc
|
||||
>>> gc.disable()
|
||||
|
||||
>>> x = []
|
||||
>>> l = [x]
|
||||
>>> n2.first = l
|
||||
>>> n2.first
|
||||
[[]]
|
||||
>>> l.append(n2)
|
||||
>>> del l
|
||||
>>> del n1
|
||||
>>> del n2
|
||||
>>> class Subclass(custom4.Custom): pass
|
||||
...
|
||||
>>> s = Subclass()
|
||||
>>> s.cycle = [s]
|
||||
>>> s.cycle.append(s.cycle)
|
||||
>>> x = object()
|
||||
>>> s.x = x
|
||||
>>> del s
|
||||
>>> sys.getrefcount(x)
|
||||
3
|
||||
>>> ignore = gc.collect()
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Modernize documentation for writing C extension types.
|
Loading…
Reference in New Issue