Sharing Declarations Between Cython Modules

This section describes how to make C declarations, functions and extensiontypes in one Cython module available for use in another Cython module.These facilities are closely modeled on the Python import mechanism,and can be thought of as a compile-time version of it.

Definition and Implementation files

A Cython module can be split into two parts: a definition file with a .pxdsuffix, containing C declarations that are to be available to other Cythonmodules, and an implementation file with a .pyx suffix, containingeverything else. When a module wants to use something declared in anothermodule’s definition file, it imports it using the cimportstatement.

A .pxd file that consists solely of extern declarations does not needto correspond to an actual .pyx file or Python module. This can make it aconvenient place to put common declarations, for example declarations offunctions from an external library that onewants to use in several modules.

What a Definition File contains

A definition file can contain:

  • Any kind of C type declaration.
  • extern C function or variable declarations.
  • Declarations of C functions defined in the module.
  • The definition part of an extension type (see below).

It cannot contain the implementations of any C or Python functions, or anyPython class definitions, or any executable statements. It is needed when onewants to access cdef attributes and methods, or to inherit fromcdef classes defined in this module.

Note

You don’t need to (and shouldn’t) declare anything in a declaration filepublic in order to make it available to other Cython modules; its merepresence in a definition file does that. You only need a publicdeclaration if you want to make something available to external C code.

What an Implementation File contains

An implementation file can contain any kind of Cython statement, although thereare some restrictions on the implementation part of an extension type if thecorresponding definition file also defines that type (see below).If one doesn’t need to cimport anything from this module, then thisis the only file one needs.

The cimport statement

The cimport statement is used in a definition orimplementation file to gain access to names declared in another definitionfile. Its syntax exactly parallels that of the normal Python importstatement:

  1. cimport module [, module...]
  2.  
  3. from module cimport name [as name] [, name [as name] ...]

Here is an example. dishes.pxd is a definition file which exports aC data type. restaurant.pyx is an implementation file which imports anduses it.

dishes.pxd:

  1. cdef enum otherstuff:
  2. sausage, eggs, lettuce
  3.  
  4. cdef struct spamdish:
  5. int oz_of_spam
  6. otherstuff filler

restaurant.pyx:

  1. from __future__ import print_function
  2. cimport dishes
  3. from dishes cimport spamdish
  4.  
  5. cdef void prepare(spamdish *d):
  6. d.oz_of_spam = 42
  7. d.filler = dishes.sausage
  8.  
  9. def serve():
  10. cdef spamdish d
  11. prepare(&d)
  12. print(f'{d.oz_of_spam} oz spam, filler no. {d.filler}')

It is important to understand that the cimport statement can onlybe used to import C data types, C functions and variables, and extensiontypes. It cannot be used to import any Python objects, and (with oneexception) it doesn’t imply any Python import at run time. If you want torefer to any Python names from a module that you have cimported, you will haveto include a regular import statement for it as well.

The exception is that when you use cimport to import an extension type, itstype object is imported at run time and made available by the name under whichyou imported it. Using cimport to import extension types is covered in moredetail below.

If a .pxd file changes, any modules that cimport from it may need to berecompiled. The Cython.Build.cythonize utility can take care of this for you.

Search paths for definition files

When you cimport a module called modulename, the Cythoncompiler searches for a file called modulename.pxd.It searches for this file along the path for include files(as specified by -I command line options or the include_pathoption to cythonize()), as well as sys.path.

Using package_data to install .pxd files in your setup.py scriptallows other packages to cimport items from your module as a dependency.

Also, whenever you compile a file modulename.pyx, the correspondingdefinition file modulename.pxd is first searched for along theinclude path (but not sys.path), and if found, it is processed beforeprocessing the .pyx file.

Using cimport to resolve naming conflicts

The cimport mechanism provides a clean and simple way to solve theproblem of wrapping external C functions with Python functions of the samename. All you need to do is put the extern C declarations into a .pxd filefor an imaginary module, and cimport that module. You can thenrefer to the C functions by qualifying them with the name of the module.Here’s an example:

c_lunch.pxd:

  1. cdef extern from "lunch.h":
  2. void eject_tomato(float)

lunch.pyx:

  1. cimport c_lunch
  2.  
  3. def eject_tomato(float speed):
  4. c_lunch.eject_tomato(speed)

You don’t need any c_lunch.pyx file, because the only things definedin c_lunch.pxd are extern C entities. There won’t be any actualc_lunch module at run time, but that doesn’t matter; thec_lunch.pxd file has done its job of providing an additional namespaceat compile time.

Sharing C Functions

C functions defined at the top level of a module can be made available viacimport by putting headers for them in the .pxd file, forexample:

volume.pxd:

  1. cdef float cube(float)

volume.pyx:

  1. cdef float cube(float x):
  2. return x * x * x

spammery.pyx:

  1. from __future__ import print_function
  2.  
  3. from volume cimport cube
  4.  
  5. def menu(description, size):
  6. print(description, ":", cube(size),
  7. "cubic metres of spam")
  8.  
  9. menu("Entree", 1)
  10. menu("Main course", 3)
  11. menu("Dessert", 2)

Note

When a module exports a C function in this way, an object appears in themodule dictionary under the function’s name. However, you can’t make use ofthis object from Python, nor can you use it from Cython using a normal importstatement; you have to use cimport.

Sharing Extension Types

An extension type can be made available via cimport by splittingits definition into two parts, one in a definition file and the other in thecorresponding implementation file.

The definition part of the extension type can only declare C attributes and Cmethods, not Python methods, and it must declare all of that type’s Cattributes and C methods.

The implementation part must implement all of the C methods declared in thedefinition part, and may not add any further C attributes. It may also definePython methods.

Here is an example of a module which defines and exports an extension type,and another module which uses it:

shrubbing.pxd:

  1. cdef class Shrubbery:
  2. cdef int width
  3. cdef int length

shrubbing.pyx:

  1. cdef class Shrubbery:
  2. def __cinit__(self, int w, int l):
  3. self.width = w
  4. self.length = l
  5.  
  6. def standard_shrubbery():
  7. return Shrubbery(3, 7)

landscaping.pyx:

  1. cimport shrubbing
  2. import shrubbing
  3.  
  4. def main():
  5. cdef shrubbing.Shrubbery sh
  6. sh = shrubbing.standard_shrubbery()
  7. print("Shrubbery size is", sh.width, 'x', sh.length)

One would then need to compile both of these modules, e.g. using

setup.py:

  1. from setuptools import setup
  2. from Cython.Build import cythonize
  3.  
  4. setup(ext_modules=cythonize(["landscaping.pyx", "shrubbing.pyx"]))

Some things to note about this example:

  • There is a cdef class Shrubbery declaration in bothShrubbing.pxd and Shrubbing.pyx. When the Shrubbing moduleis compiled, these two declarations are combined into one.
  • In Landscaping.pyx, the cimport Shrubbing declaration allows usto refer to the Shrubbery type as Shrubbing.Shrubbery. But itdoesn’t bind the name Shrubbing in Landscaping’s module namespace at runtime, so to access Shrubbing.standard_shrubbery() we also need toimport Shrubbing.
  • One caveat if you use setuptools instead of distutils, the defaultaction when running python setup.py install is to create a zippedegg file which will not work with cimport for pxd fileswhen you try to use them from a dependent package.To prevent this, include zip_safe=False in the arguments to setup().