ctypes <https://docs.python.org/3/library/ctypes.html> is a wonderful
library. If you need to create a Python binding for some library that
doesn’t already have one, you should look at ctypes, rather than
writing an extension module in C or C++. The odds are, a pure Python
implementation using ctypes can be done with much less effort than
writing C/C++ code.

But it is not enough to wrap an API designed for C or C++. A Python
API should take advantage of the power and convenience of Python. The
question I like to ask is: “How would the API have been designed if it
was native to Python, rather than being intended for C or C++?”.
Another question to consider is: “Does the Python API binding give you
a reason to use Python to write your code in the first place, rather
than choosing C or C++ instead?”. In short, does your Python code that
makes calls to the API look like a straight transliteration of C code,
or does it work at a higher level?

What do I mean by this? An obvious, very basic, example is not having
the user have to bother with explicitly creating and destroying
objects. Wrap an underlying API object in a Python object, such that
the Python object’s “__del__” method will take care of disposing of
the API object. Then Python’s usual object-management mechanisms will
automatically manage the API objects as well. This is how Qahirah
<https://github.com/ldo/qahirah>, my wrapper for the Cairo graphics
library <https://www.cairographics.org/>, works.

But there is more you can do. Another obvious one is, where the
underlying API provides “get_xxx” and “set_xxx” methods, to wrap them
in a read/write Python property.

For example, Cairo defines a “fill rule” that governs how paths are
filled, with getter and setter calls
<https://www.cairographics.org/manual/cairo-cairo-t.html#cairo-set-fill-rule>.
In Qahirah this is translated to a property that you can obtain with a
simple expression like “«ctx».fill_rule” (where «ctx» is a Context
object), and change by a simple assignment like “«ctx».fill_rule =
«new_value»”.

Sometimes the values that are returned by the getter or set with the
setter have a more complex, variable-length structure. So the C API
may need two parts to the getter routines, one to determine how big a
structure the caller needs to allocate, and the other to fill in a
preallocated structure with the actual data. For example, the dash
setting for stroking lines
<https://www.cairographics.org/manual/cairo-cairo-t.html#cairo-set-dash>
is of this type.

In Python, it makes sense to store the dash information in a
high-level Python object, which is directly returned from the
Python-level getter and directly passed to the Python-level setter. In
Qahirah, I use a 2-tuple, the first element of which specifies the
alternating on/off dash lengths (itself a tuple), and the second of
which is the starting offset. All conversion between this format and
the actual format that Cairo expects happens within the Python wrapper
routines, without the caller having to worry about it.

Sometimes the opportunities to add value come from other available
Python libraries. Cairo only deals with colours as RGB components,
with an optional alpha. But Python provides this handy colorsys module
<https://docs.python.org/3/library/colorsys.html> as standard, which
provides conversions to/from a number of non-RGB colour spaces; why
not take advantage of it?

So I defined a “Colour” type, and all wrappers to Cairo calls that set
or get colours take or return this object instead of separate RGB or
RGBA components. A Colour can be constructed from components in any of
the supported colour spaces, and you can query its components in any
of these colour spaces.

But more than that, you can also perform useful manipulations on
Colours, like adjusting the values of components to produce new, but
related, colours. This all goes way beyond the functionality of Cairo
itself, but I felt it was useful, even essential, to have in a
worthwhile graphics API.

Python has a remarkably simple and elegant technique for implementing
custom overloading of its standard operators. C, of course, has no
such thing. So APIs designed for C have to provide function calls
instead, such as for Cairo’s matrix operations
<https://www.cairographics.org/manual/cairo-cairo-matrix-t.html>.

It was easy enough for me to define a “Matrix” class, with the “*”
operator (“@” as well in Python 3.5 or later) to do Matrix
multiplication. Instead of updating a Matrix object in place
(procedural programming), I prefer a more functional approach, where
each operation creates a new Matrix object as its result, leaving its
operands unchanged. This lets you write expressions which are closer
to the underlying mathematics.

I also defined a “Vector” class, representing both X and Y coordinates
in a single value. Cairo itself requires you to pass (or get back)
separate X and Y coordinate values; but again, I wrap all these calls
so that you pass and return Vectors. It is extremely common to do
calculations on corresponding X- and Y-coordinates that are absolutely
identical, except for the coordinate-specific components; Qahirah lets
you write these calculations just once instead of twice, operating
directly on Vectors.

In short: ctypes is cool. Python is cool. Python + ctypes = OMG wow
cool. Yeah. :)
-- 
https://mail.python.org/mailman/listinfo/python-list

Reply via email to