Using C++ in Cython¶
Overview¶
Cython has native support for most of the C++ language. Specifically:
C++ objects can bedynamically allocated with
new
anddel
keywords.C++ objects can bestack-allocated.
C++ classes can be declared with the new keyword
cppclass
.Templated classes and functions are supported.
Overloaded functions are supported.
Overloading of C++ operators (such as
operator+
,operator[]
, …) is supported.
Procedure Overview¶
The general procedure for wrapping a C++ file can now be described as follows:
Specify C++ language in a
setup.py
script or locally in a source file.Create one or more
.pxd
files withcdefexternfrom
blocks and(if existing) the C++ namespace name. In these blocks:declare classes as
cdefcppclass
blocksdeclare public names (variables, methods and constructors)
cimport
them in one or more extension modules (.pyx
files).
A simple Tutorial¶
An example C++ API¶
Here is a tiny C++ API which we will use as an example throughout thisdocument. Let’s assume it will be in a header file calledRectangle.h
:
#ifndef RECTANGLE_H#define RECTANGLE_Hnamespaceshapes{classRectangle{public:intx0,y0,x1,y1;Rectangle();Rectangle(intx0,inty0,intx1,inty1);~Rectangle();intgetArea();voidgetSize(int*width,int*height);voidmove(intdx,intdy);};}#endif
and the implementation in the file calledRectangle.cpp
:
#include<iostream>#include"Rectangle.h"namespaceshapes{// Default constructorRectangle::Rectangle(){}// Overloaded constructorRectangle::Rectangle(intx0,inty0,intx1,inty1){this->x0=x0;this->y0=y0;this->x1=x1;this->y1=y1;}// DestructorRectangle::~Rectangle(){}// Return the area of the rectangleintRectangle::getArea(){return(this->x1-this->x0)*(this->y1-this->y0);}// Get the size of the rectangle.// Put the size in the pointer argsvoidRectangle::getSize(int*width,int*height){(*width)=x1-x0;(*height)=y1-y0;}// Move the rectangle by dx dyvoidRectangle::move(intdx,intdy){this->x0+=dx;this->y0+=dy;this->x1+=dx;this->y1+=dy;}}
This is pretty dumb, but should suffice to demonstrate the steps involved.
Declaring a C++ class interface¶
The procedure for wrapping a C++ class is quite similar to that for wrappingnormal C structs, with a couple of additions. Let’s start here by creating thebasiccdefexternfrom
block:
cdefexternfrom"Rectangle.h"namespace"shapes":
This will make the C++ class def for Rectangle available. Note the namespace declaration.Namespaces are simply used to make the fully qualified name of the object,and can be nested (e.g."outer::inner"
) or even refer toclasses (e.g."namespace::MyClass
to declare static members on MyClass).
Declare class with cdef cppclass¶
Now, let’s add the Rectangle class to this extern from block - just copy theclass name from Rectangle.h and adjust for Cython syntax, so now it becomes:
cdefexternfrom"Rectangle.h"namespace"shapes":cdefcppclassRectangle:
Add public attributes¶
We now need to declare the attributes and methods for use on Cython. We put those declarationsin a file calledRectangle.pxd
. You can see it as a header filewhich is readable by Cython:
cdefexternfrom"Rectangle.cpp":pass# Declare the class with cdefcdefexternfrom"Rectangle.h"namespace"shapes":cdefcppclassRectangle:Rectangle()except+Rectangle(int,int,int,int)except+intx0,y0,x1,y1intgetArea()voidgetSize(int*width,int*height)voidmove(int,int)
Note that the constructor is declared as “except +”. If the C++ code orthe initial memory allocation raises an exception due to a failure, thiswill let Cython safely raise an appropriate Python exception instead(see below). Without this declaration, C++ exceptions originating fromthe constructor will not be handled by Cython.
We use the lines:
cdefexternfrom"Rectangle.cpp":pass
to include the C++ code fromRectangle.cpp
. It is also possible to specify tosetuptools thatRectangle.cpp
is a source. To do that, you can add this directive at thetop of the.pyx
(not.pxd
) file:
# distutils: sources = Rectangle.cpp
Note that when you usecdefexternfrom
, the path that you specify is relative to the currentfile, but if you use the distutils directive, the path is relative to thesetup.py
. If you want to discover the path of the sources whenrunning thesetup.py
, you can use thealiases
argumentof thecythonize()
function.
Declare a var with the wrapped C++ class¶
We’ll create a.pyx
file namedrect.pyx
to build our wrapper. We’reusing a name other thanRectangle
, but if you prefer giving the same nameto the wrapper as the C++ class, see the section onresolving naming conflicts.
Within, we use cdef to declare a var of the class with the C++new
statement:
# distutils: language = c++fromRectanglecimportRectangledefmain():rec_ptr=newRectangle(1,2,3,4)# Instantiate a Rectangle object on the heaptry:rec_area=rec_ptr.getArea()finally:delrec_ptr# delete heap allocated objectcdefRectanglerec_stack# Instantiate a Rectangle object on the stack
The line:
# distutils: language = c++
is to indicate to Cython that this.pyx
file has to be compiled to C++.
It’s also possible to declare a stack allocated object, as long as it hasa “default” constructor:
cdefexternfrom"Foo.h":cdefcppclassFoo:Foo()deffunc():cdefFoofoo...
See the section on thecpp_locals directive for a wayto avoid requiring a nullary/default constructor.
Note that, like C++, if the class has only one constructor and itis a nullary one, it’s not necessary to declare it.
Create Cython wrapper class¶
At this point, we have exposed into our pyx file’s namespace the interfaceof the C++ Rectangle type. Now, we need to make this accessible fromexternal Python code (which is our whole point).
Common programming practice is to create a Cython extension type whichholds a C++ instance as an attribute and create a bunch offorwarding methods. So we can implement the Python extension type as:
# distutils: language = c++fromRectanglecimportRectangle# Create a Cython extension type which holds a C++ instance# as an attribute and create a bunch of forwarding methods# Python extension type.cdefclassPyRectangle:cdefRectanglec_rect# Hold a C++ instance which we're wrappingdef__init__(self,intx0,inty0,intx1,inty1):self.c_rect=Rectangle(x0,y0,x1,y1)defget_area(self):returnself.c_rect.getArea()defget_size(self):cdefintwidth,heightself.c_rect.getSize(&width,&height)returnwidth,heightdefmove(self,dx,dy):self.c_rect.move(dx,dy)
And there we have it. From a Python perspective, this extension type will lookand feel just like a natively defined Rectangle class.It should be noted that if you want to giveattribute access, you could just implement some properties:
# distutils: language = c++fromRectanglecimportRectanglecdefclassPyRectangle:cdefRectanglec_rectdef__init__(self,intx0,inty0,intx1,inty1):self.c_rect=Rectangle(x0,y0,x1,y1)defget_area(self):returnself.c_rect.getArea()defget_size(self):cdefintwidth,heightself.c_rect.getSize(&width,&height)returnwidth,heightdefmove(self,dx,dy):self.c_rect.move(dx,dy)# Attribute access@propertydefx0(self):returnself.c_rect.x0@x0.setterdefx0(self,x0):self.c_rect.x0=x0# Attribute access@propertydefx1(self):returnself.c_rect.x1@x1.setterdefx1(self,x1):self.c_rect.x1=x1# Attribute access@propertydefy0(self):returnself.c_rect.y0@y0.setterdefy0(self,y0):self.c_rect.y0=y0# Attribute access@propertydefy1(self):returnself.c_rect.y1@y1.setterdefy1(self,y1):self.c_rect.y1=y1
Cython initializes C++ class attributes of a cdef class using the nullary constructor.If the class you’re wrapping does not have a nullary constructor, you must store a pointerto the wrapped class and manually allocate and deallocate it. Alternatively, thecpp_locals directive avoids the need for the pointer and only initializes theC++ class attribute when it is assigned to.A convenient and safe place to do so is in the__cinit__ and__dealloc__ methodswhich are guaranteed to be called exactly once upon creation and deletion of the Pythoninstance.
# distutils: language = c++fromRectanglecimportRectanglecdefclassPyRectangle:cdefRectangle*c_rect# hold a pointer to the C++ instance which we're wrappingdef__cinit__(self):self.c_rect=newRectangle()def__init__(self,intx0,inty0,intx1,inty1):self.c_rect.x0=x0self.c_rect.y0=y0self.c_rect.x1=x1self.c_rect.y1=y1def__dealloc__(self):delself.c_rect
Compilation and Importing¶
To compile a Cython module, it is necessary to have asetup.py
file:
fromsetuptoolsimportsetupfromCython.Buildimportcythonizesetup(ext_modules=cythonize("rect.pyx"))
Run$pythonsetup.pybuild_ext--inplace
To test it, open the Python interpreter:
>>>importrect>>>x0,y0,x1,y1=1,2,3,4>>>rect_obj=rect.PyRectangle(x0,y0,x1,y1)>>>print(dir(rect_obj))['__class__','__delattr__','__dir__','__doc__','__eq__','__format__','__ge__','__getattribute__','__gt__','__hash__','__init__','__init_subclass__','__le__','__lt__','__ne__','__new__','__reduce__','__reduce_ex__','__repr__','__setattr__','__setstate__','__sizeof__','__str__','__subclasshook__','get_area','get_size','move']
Advanced C++ features¶
We describe here all the C++ features that were not discussed in the above tutorial.
Overloading¶
Overloading is very simple. Just declare the method with different parametersand use any of them:
cdefexternfrom"Foo.h":cdefcppclassFoo:Foo(int)Foo(bool)Foo(int,bool)Foo(int,int)
Overloading operators¶
Cython uses C++ naming for overloading operators:
cdefexternfrom"foo.h":cdefcppclassFoo:Foo()Foooperator+(Foo)Foooperator-(Foo)intoperator*(Foo)intoperator/(int)intoperator*(int,Foo)# allows 1*Foo()# nonmember operators can also be specified outside the classdoubleoperator/(double,Foo)cdefFoofoo=newFoo()foo2=foo+foofoo2=foo-foox=foo*foo2x=foo/1x=foo[0]*foo2x=foo[0]/1x=1*foo[0]cdefdoubleyy=2.0/foo[0]
Note that if one haspointers to C++ objects, dereferencing must be doneto avoid doing pointer arithmetic rather than arithmetic on the objectsthemselves:
cdefFoo*foo_ptr=newFoo()foo=foo_ptr[0]+foo_ptr[0]x=foo_ptr[0]/2delfoo_ptr
Nested class declarations¶
C++ allows nested class declaration. Class declarations can also benested in Cython:
# distutils: language = c++cdefexternfrom"<vector>"namespace"std":cdefcppclassvector[T]:cppclassiterator:Toperator*()iteratoroperator++()bintoperator==(iterator)bintoperator!=(iterator)vector()voidpush_back(T&)T&operator[](int)T&at(int)iteratorbegin()iteratorend()cdefvector[int].iteratoriter#iter is declared as being of type vector<int>::iterator
Note that the nested class is declared with acppclass
but without acdef
,as it is already part of acdef
declaration section.
C++ operators not compatible with Python syntax¶
Cython tries to keep its syntax as close as possible to standard Python.Because of this, certain C++ operators, like the preincrement++foo
or the dereferencing operator*foo
cannot be used with the samesyntax as C++. Cython provides functions replacing these operators ina special modulecython.operator
. The functions provided are:
cython.operator.dereference
for dereferencing.dereference(foo)
will produce the C++ code*(foo)
cython.operator.preincrement
for pre-incrementation.preincrement(foo)
will produce the C++ code++(foo)
.Similarly forpredecrement
,postincrement
andpostdecrement
.cython.operator.comma
for the comma operator.comma(a,b)
will produce the C++ code((a),(b))
.
These functions need to be cimported. Of course, one can use afrom...cimport...as
to have shorter and more readable functions.For example:fromcython.operatorcimportdereferenceasderef
.
For completeness, it’s also worth mentioningcython.operator.address
which can also be written&foo
.
Templates¶
Cython uses a bracket syntax for templating. A simple example for wrapping C++ vector:
# distutils: language = c++# import dereference and increment operatorsfromcython.operatorcimportdereferenceasderef,preincrementasinccdefexternfrom"<vector>"namespace"std":cdefcppclassvector[T]:cppclassiterator:Toperator*()iteratoroperator++()bintoperator==(iterator)bintoperator!=(iterator)vector()voidpush_back(T&)T&operator[](int)T&at(int)iteratorbegin()iteratorend()cdefvector[int] *v=newvector[int]()cdefintiforiinrange(10):v.push_back(i)cdefvector[int].iteratorit=v.begin()whileit!=v.end():print(deref(it))inc(it)delv
Multiple template parameters can be defined as a list, such as[T,U,V]
or[int,bool,char]
. Optional template parameters can be indicatedby writing[T,U,V=*]
. In the event that Cython needs to explicitlyreference the type of a default template parameter for an incomplete templateinstantiation, it will writeMyClass<T,U>::V
, so if the class providesa typedef for its template parameters it is preferable to use that name here.
Template functions are defined similarly to class templates, withthe template parameter list following the function name:
# distutils: language = c++cdefexternfrom"<algorithm>"namespace"std":Tmax[T](Ta,Tb)print(max[long](3,4))print(max(1.5,2.5))# simple template argument deduction
Standard library¶
Most of the containers of the C++ Standard Library have been declaredin pxd files locatedin/Cython/Includes/libcpp.These containers are: deque, list, map, pair, queue, set, stack, vector.
For example:
# distutils: language = c++fromlibcpp.vectorcimportvectorcdefvector[int]vectcdefinti,xforiinrange(10):vect.push_back(i)foriinrange(10):print(vect[i])forxinvect:print(x)
The pxd filesin/Cython/Includes/libcppalso work as good examples on how to declare C++ classes.
The STL containers coerce from and to thecorresponding Python builtin types. The conversion is triggeredeither by an assignment to a typed variable (including typed functionarguments) or by an explicit cast, e.g.:
# cython: language_level=3# distutils: language=c++fromlibcpp.complexcimportcomplex,conjfromlibcpp.stringcimportstringfromlibcpp.vectorcimportvectorpy_bytes_object=b'The knights who say ni'py_unicode_object=u'Those who hear them seldom live to tell the tale.'cdefstrings=py_bytes_objectprint(s)# b'The knights who say ni'cdefstringcpp_string=<string>py_unicode_object.encode('utf-8')print(cpp_string)# b'Those who hear them seldom live to tell the tale.'cdefvector[int]vect=range(1,10,2)print(vect)# [1, 3, 5, 7, 9]cdefvector[string]cpp_strings=b'It is a good shrubbery'.split()print(cpp_strings[1])# b'is'# creates a python object, then convert it to C++ complexcomplex_val=1+2jcdefcomplex[double]c_value1=complex_valprint(c_value1)# (1+2j)# transforms a C++ object to another one without Python conversioncdefcomplex[double]c_value2=conj(c_value1)print(c_value2)# (1-2j)
The following coercions are available:
Python type => | C++ type | => Python type |
---|---|---|
bytes | std::string | bytes |
iterable | std::vector | list |
iterable | std::list | list |
iterable | std::set | set |
iterable | std::unordered_set | set |
mapping | std::map | dict |
mapping | std::unordered_map | dict |
iterable (len 2) | std::pair | tuple (len 2) |
complex | std::complex | complex |
All conversions create a new container and copy the data into it.The items in the containers are converted to a corresponding typeautomatically, which includes recursively converting containersinside of containers, e.g. a C++ vector of maps of strings.
Be aware that the conversions do have some pitfalls, which aredetailed inthe troubleshooting section.
Iteration over stl containers (or indeed any class withbegin()
andend()
methods returning an object supporting incrementing, dereferencing,and comparison) is supported via thefor..in
syntax (including in listcomprehensions). For example, one can write:
# distutils: language = c++fromlibcpp.vectorcimportvectordefmain():cdefvector[int]v=[4,6,5,10,3]cdefintvalueforvalueinv:print(value)return[x*xforxinvifx%2==0]
If the loop target variable is unspecified, an assignment from type*container.begin()
is used fortype inference.
Note
Slicing stl containers is supported,you can doforxinmy_vector[:5]:...
but unlike pointers slices,it will create a temporary Python object and iterate over it. Thusmaking the iteration very slow. You might want to avoid slicingC++ containers for performance reasons.
Simplified wrapping with default constructor¶
If your extension type instantiates a wrapped C++ class using the defaultconstructor (not passing any arguments), you may be able to simplify thelifecycle handling by tying it directly to the lifetime of the Python wrapperobject. Instead of a pointer attribute, you can declare an instance:
# distutils: language = c++fromlibcpp.vectorcimportvectorcdefclassVectorStack:cdefvector[int]vdefpush(self,x):self.v.push_back(x)defpop(self):ifself.v.empty():raiseIndexError()x=self.v.back()self.v.pop_back()returnx
Cython will automatically generate code that instantiates the C++ objectinstance when the Python object is created and deletes it when the Pythonobject is garbage collected.
Exceptions¶
Cython cannot throw C++ exceptions, or catch them with a try-except statement,but it is possible to declare a function as potentially raising an C++exception and converting it into a Python exception. For example,
cdefexternfrom"some_file.h":cdefintfoo()except+
This will translate try and the C++ error into an appropriate Python exception.The translation is performed according to the following table(thestd::
prefix is omitted from the C++ identifiers):
C++ | Python |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(all others) |
|
Thewhat()
message, if any, is preserved. Note that a C++ios_base_failure
can denote EOF, but does not carry enough informationfor Cython to discern that, so watch out with exception masks on IO streams.
cdefintbar()except+MemoryError
This will catch any C++ error and raise a Python MemoryError in its place.(Any Python exception is valid here.)
Cython also supports using a custom exception handler. This is an advanced featurethat most users won’t need, but for those that do a full example follows:
cdefintraise_py_error()cdefintsomething_dangerous()except+raise_py_error
If something_dangerous raises a C++ exception then raise_py_error will becalled, which allows one to do custom C++ to Python error “translations.” Ifraise_py_error does not actually raise an exception a RuntimeError will beraised. This approach may also be used to manage custom Python exceptionscreated using the Python C API.
# raising.pxdcdefexternfrom"Python.h"nogil:ctypedefstructPyObjectcdefexternfrom*:""" #include <Python.h> #include <stdexcept> #include <ios> PyObject *CustomLogicError; void create_custom_exceptions() { CustomLogicError = PyErr_NewException("raiser.CustomLogicError", NULL, NULL); } void custom_exception_handler() { try { if (PyErr_Occurred()) { ; // let the latest Python exn pass through and ignore the current one } else { throw; } } catch (const std::logic_error& exn) { // Add mapping of std::logic_error -> CustomLogicError PyErr_SetString(CustomLogicError, exn.what()); } catch (...) { PyErr_SetString(PyExc_RuntimeError, "Unknown exception"); } } class Raiser { public: Raiser () {} void raise_exception() { throw std::logic_error("Failure"); } }; """cdefPyObject*CustomLogicErrorcdefvoidcreate_custom_exceptions()cdefvoidcustom_exception_handler()cdefcppclassRaiser:Raiser()noexceptvoidraise_exception()except+custom_exception_handler# raising.pyxcreate_custom_exceptions()PyCustomLogicError=<object>CustomLogicErrorcdefclassPyRaiser:cdefRaiserc_objdefraise_exception(self):self.c_obj.raise_exception()
The above example leverages Cython’s ability to includeverbatim C code in pxd files to create a new Python exception typeCustomLogicError
and map it to the standard C++std::logic_error
usingthecustom_exception_handler
function. There is nothing special about usinga standard exception class here,std::logic_error
could easily be replacedwith some new C++ exception type defined in this file. TheRaiser::raise_exception
is marked with+custom_exception_handler
toindicate that this function should be called whenever an exception is raised.The corresponding Python functionPyRaiser.raise_exception
will raise aCustomLogicError
whenever it is called. DefiningPyCustomLogicError
allows other code to catch this exception, as shown below:
try:PyRaiser().raise_exception()exceptPyCustomLogicError:print("Caught the exception")
When defining custom exception handlers it is typically good to also includelogic to handle all the standard exceptions that Cython typically handles aslisted in the table above. The code for this standard exception handler can befoundhere.
If you want to usestd::exception_ptr then you cancimport
it fromlibcpp.exception
. That provides a special exception handlerexception_ptr_error_handler
allowing you to declare a function:
cdefexternfrom"some_header":voidsome_function()except+exception_ptr_error_handler
The exception raised fromsome_function
will always be anException
and the underlyingstd::exception_ptr
can be retrieved withwrapped_exception_ptr_from_exception
. This is a slightly niche use,butstd::exception_ptr
is a useful way to safely store arbitrary C++exceptions for later.
There is also the special form:
cdefintraise_py_or_cpp()except+*
for those functions that may raise either a Python or a C++ exception.
Static member method¶
If the Rectangle class has a static member:
namespaceshapes{classRectangle{...public:staticvoiddo_something();};}
you can declare it using the Python @staticmethod decorator, i.e.:
cdefexternfrom"Rectangle.h"namespace"shapes":cdefcppclassRectangle:...@staticmethodvoiddo_something()
Declaring/Using References¶
Cython supports declaring lvalue references using the standardType&
syntax.Note, however, that it is unnecessary to declare the arguments of externfunctions as references (const or otherwise) as it has no impact on thecaller’s syntax.
Scoped Enumerations¶
Cython supports scoped enumerations (enumclass
) in C++ mode:
cdefenumclassCheese:cheddar=1camembert=2
As with “plain” enums, you may access the enumerators as attributes of the type.Unlike plain enums however, the enumerators are not visible to theenclosing scope:
cdefCheesec1=Cheese.cheddar# OKcdefCheesec2=cheddar# ERROR!
Optionally, you may specify the underlying type of a scoped enumeration.This is especially important when declaring an external scoped enumerationwith an underlying type:
cdefexternfrom"Foo.h":cdefenumclassSpam(unsignedint):x=10y=20...
Declaring an enum class ascpdef
will create aPEP 435-style Python wrapper.
auto
Keyword¶
Though Cython does not have anauto
keyword, Cython local variablesnot explicitly typed withcdef
are deduced from the types of the right handside ofall their assignments (see theinfer_types
compiler directive). This is particularly handywhen dealing with functions that return complicated, nested, templated types,e.g.:
cdefvector[int]v=...it=v.begin()
(Though of course thefor..in
syntax is preferred for objects supportingthe iteration protocol.)
RTTI and typeid()¶
Cython has support for thetypeid(...)
operator.
from cython.operator cimport typeid
Thetypeid(...)
operator returns an object of the typeconsttype_info&
.
If you want to store a type_info value in a C variable, you will need to store itas a pointer rather than a reference:
fromlibcpp.typeinfocimporttype_infocdefconsttype_info*info=&typeid(MyClass)
If an invalid type is passed totypeid
, it will throw anstd::bad_typeid
exception which is converted into aTypeError
exception in Python.
An additional C++11-only RTTI-related class,std::type_index
, is availableinlibcpp.typeindex
.
Specify C++ language in setup.py¶
Instead of specifying the language and the sources in the source files, it ispossible to declare them in thesetup.py
file:
fromsetuptoolsimportsetupfromCython.Buildimportcythonizesetup(ext_modules=cythonize("rect.pyx",# our Cython sourcesources=["Rectangle.cpp"],# additional source file(s)language="c++",# generate C++ code))
Cython will generate and compile therect.cpp
file (fromrect.pyx
), then it will compileRectangle.cpp
(implementation of theRectangle
class) and link both object filestogether intorect.so
on Linux, orrect.pyd
on windows,which you can then import in Python usingimportrect
(if you forget to link theRectangle.o
, you willget missing symbols while importing the library in Python).
Note that thelanguage
option has no effect on user provided Extensionobjects that are passed intocythonize()
. It is only used for modulesfound by file name (as in the example above).
Thecythonize()
function in Cython versions up to 0.21 does notrecognize thelanguage
option and it needs to be specified as anoption to anExtension
that describes your extension and thatis then handled bycythonize()
as follows:
fromsetuptoolsimportExtension,setupfromCython.Buildimportcythonizesetup(ext_modules=cythonize(Extension("rect",# the extension namesources=["rect.pyx","Rectangle.cpp"],# the Cython source and# additional C++ source fileslanguage="c++",# generate and compile C++ code)))
The options can also be passed directly from the source file, which isoften preferable (and overrides any global option). Starting withversion 0.17, Cython also allows passing external source files into thecythonize()
command this way. Here is a simplified setup.py file:
fromsetuptoolsimportsetupfromCython.Buildimportcythonizesetup(name="rectangleapp",ext_modules=cythonize('*.pyx'),)
And in the .pyx source file, write this into the first comment block, beforeany source code, to compile it in C++ mode and link it statically against theRectangle.cpp
code file:
# distutils: language = c++# distutils: sources = Rectangle.cpp
Note
When using distutils directives, the paths are relative to the workingdirectory of the setuptools run (which is usually the project root wherethesetup.py
resides).
To compile manually (e.g. usingmake
), thecython
command-lineutility can be used to generate a C++.cpp
file, and then compile itinto a python extension. C++ mode for thecython
command is turnedon with the--cplus
option.
cpp_locals
directive¶
Thecpp_locals
compiler directive is an experimental feature that makesC++ variables behave like normal Python object variables. With thisdirective they are only initialized at their first assignment, and thusthey no longer require a nullary constructor to be stack-allocated. Trying toaccess an uninitialized C++ variable will generate anUnboundLocalError
(or similar) in the same way as a Python variable would. For example:
deffunction(dont_write):cdefSomeCppClassc# not initializedifdont_write:returnc.some_cpp_function()# UnboundLocalErrorelse:c=SomeCppClass(...)# initializedreturnc.some_cpp_function()# OK
Additionally, the directive avoids initializing temporary C++ objects beforethey are assigned, for cases where Cython needs to use such objects in itsown code-generation (often for return values of functions that can throwexceptions).
For extra speed, theinitializedcheck
directive disables the check for anunbound-local. With this directive on, accessing a variable that has notbeen initialized will trigger undefined behaviour, and it is entirely the user’sresponsibility to avoid such access.
Thecpp_locals
directive is currently implemented usingstd::optional
and thus requires a C++17 compatible compiler. DefiningCYTHON_USE_BOOST_OPTIONAL
(as define for the C++ compiler) usesboost::optional
instead (but is even more experimental and untested). The directive maycome with a memory and performance cost due to the need to store and checka boolean that tracks if a variable is initialized, but the C++ compiler shouldbe able to eliminate the check in most cases.
Caveats and Limitations¶
Access to C-only functions¶
Whenever generating C++ code, Cython generates declarations of and callsto functions assuming these functions are C++ (ie, not declared asextern"C"{...}
. This is ok if the C functions have C++ entry points, but if they’re Conly, you will hit a roadblock. If you have a C++ Cython module needingto make calls to pure-C functions, you will need to write a small C++ shimmodule which:
includes the needed C headers in an extern “C” block
contains minimal forwarding functions in C++, each of which calls therespective pure-C function
C++ left-values¶
C++ allows functions returning a reference to be left-values. This is currentlynot supported in Cython.cython.operator.dereference(foo)
is also notconsidered a left-value.