Migrating from Cython 0.29 to 3.0¶
Cython 3.0 is a major revision of the compiler and the languagethat comes with some backwards incompatible changes.This document lists the important ones and explains how to deal withthem in existing code.
Python 3 syntax/semantics¶
Cython 3.0 now uses Python 3 syntax and semantics by default, which previouslyrequired setting thelanguage_level
directive <compiler-directives> toeither3
or3str
.The new default setting is nowlanguage_level=3str
, which means Python 3semantics, but unprefixed strings arestr
objects, i.e. unicode text stringsunder Python 3 and byte strings under Python 2.7.
You can revert your code to the previous (Python 2.x) semantics by settinglanguage_level=2
.
Further semantic changes due to the language level include:
/
-division uses the true (float) division operator, unlesscdivision
is enabled.print
is a function, not a statement.Python classes that are defined without bases (
classC:...
) are “new-style”classes also in Py2.x (if you never heard about “old-style classes”, you’re probablyhappy without them).Annotations (type hints) are now stored as strings.(PEP 563)
StopIteration
handling in generators has been changed according toPEP 479.
Python semantics¶
Some Python compatibility bugs were fixed, e.g.
Subscripting (
x[1]
) now tries the mapping protocol before the sequence protocol.(https://github.com/cython/cython/issues/1807)Exponentiation of integer literals now follows Python semantics and not C semantics.(https://github.com/cython/cython/issues/2133)
Binding functions¶
Thebinding directive is now enabled by default.This makes Cython compiled Python (def
) functions mostly compatiblewith normal (non-compiled) Python functions, regarding signature introspection,annotations, etc.
It also makes them bind as methods in Python classes on attribute assignments,thus the name.If this is not intended, i.e. if a function is really meant to be a functionand never a method, you can disable the binding (and all other Python functionfeatures) by settingbinding=False
or selectively adding a decorator@cython.binding(False)
.In pure Python mode, the decorator was not available in Cython 0.29.16 yet,but compiled code does not suffer from this.
We recommend, however, to keep the new function features and instead dealwith the binding issue using the standard Pythonstaticmethod()
builtin.
deffunc(self,b):...classMyClass(object):binding_method=funcno_method=staticmethod(func)
Namespace packages¶
Cython now has support for loading pxd files also from namespace packagesaccording toPEP-420.This might have an impact on the import path.
NumPy C-API¶
Cython used to generate code that depended on the deprecated pre-NumPy-1.7 C-API.This is no longer the case with Cython 3.0.
You can now define the macroNPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION
to get rid of the long-standing build warnings that the compiled C moduleuses a deprecated API. Either per file:
# distutils: define_macros=NPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION
or by setting it in your Extensions insetup.py
:
Extension(...define_macros=[("NPY_NO_DEPRECATED_API","NPY_1_7_API_VERSION")])
One side-effect of the different C-API usage is that your code may nowrequire a call to theNumPy C-API initialisation functionwhere it previously got away without doing so.
In order to reduce the user impact here, Cython 3.0 will now call itautomatically when it seesnumpy
being cimported, but the functionnot being used.In the (hopefully rare) cases where this gets in the way, the internalC-API initialisation can be disabled by faking the use of the functionwithout actually calling it, e.g.
# Explicitly disable the automatic initialisation of NumPy's C-API.<void>import_array
Class-private name mangling¶
Cython has been updated to follow thePython rules for class-private namesmore closely. Essentially any name that starts with and doesn’t end with__
within a class is mangled with the class name. Most user codeshould be unaffected – unlike in Python unmangled global names willstill be matched to ensure it is possible to access C namesbeginning with__
:
cdefexternvoid__foo()classC:# or "cdef class"defcall_foo(self):return__foo()# still calls the global name
What will no-longer work is overriding methods starting with__
inacdefclass
:
cdefclassBase:cdef__bar(self):return1defcall_bar(self):returnself.__bar()cdefclassDerived(Base):cdef__bar(self):return2
HereBase.__bar
is mangled to_Base__bar
andDerived.__bar
to_Derived__bar
. Thereforecall_bar
will always call_Base__bar
. This matches established Python behaviour and appliesfordef
,cdef
andcpdef
methods and attributes.
Arithmetic special methods¶
The behaviour of arithmetic special methods (for example__add__
and__pow__
) of cdef classes has changed in Cython 3.0. They nowsupport separate “reversed” versions of these methods (e.g.__radd__
,__rpow__
) that behave like in pure Python.The main incompatible change is that the type of the first operand(usually__self__
) is now assumed to be that of the defining class,rather than relying on the user to test and cast the type of each operand.
The old behaviour can be restored with thedirectivec_api_binop_methods=True
.More details are given inArithmetic methods.
Exception values andnoexcept
¶
cdef
functions that are notextern
now safely propagate Pythonexceptions by default. Previously, they needed to explicitly be declaredwith anexception value to prevent them fromswallowing exceptions. A newnoexcept
modifier can be used to declarecdef
functions that really will not raise exceptions.
In existing code, you should mainly look out forcdef
functionsthat are declared without an exception value:
cdefintspam(intx):passcdefvoidsilent(intx):pass
If you left out the exception value by mistake, i.e., the functionshould propagate Python exceptions, then the new behaviour will takecare of this for you, and correctly propagate any exceptions.This was a common mistake in Cython code and the main reason to change the behaviour.
On the other hand, if you didn’t declare an exception value becauseyou want to avoid exceptions propagating out of this function, the new behaviourwill result in slightly less efficient code being generated, now involving an exception check.To prevent that, you must declare the function explicitly as beingnoexcept
:
cdefintspam(intx)noexcept:passcdefvoidsilent(intx)noexcept:pass
The behaviour forcdef
functions that are alsoextern
isunchanged asextern
functions are less likely to raise Pythonexceptions and rather tend to be plain C functions. This mitigatesthe effect of this change for code that talks to C libraries.
The behaviour for anycdef
function that is declared with anexplicit exception value (e.g.,cdefintspam(intx)except-1
) isalso unchanged.
There is an easy-to-encounter performance pitfall here withnogil
functionswith an implicit exception specification ofexcept*
. This can happenmost commonly when the return type isvoid
(but in principle appliesto most non-numeric return types). In this case, Cython is forced tore-acquire the GIL brieflyafter each call to check the exception state.To avoid this overhead, either change the signature tonoexcept
(ifyou have determined that it’s suitable to do so), or to returning anint
instead to let Cython use theint
as an error flag(by default,-1
triggers the exception check).
Note
The unsafe legacy behaviour of not propagating exceptions by default can be enabled bysettinglegacy_implicit_noexcept
compiler directivetoTrue
.
Annotation typing¶
Cython 3 has made substantial improvements in recognising types inannotations and it is well worth readingthe pure Python tutorial to understandsome of the improvements.
A notable backwards-incompatible change is thatx:int
is now typedsuch thatx
is an exact Pythonint
(Cython 0.29 would acceptany Python object forx
), unless the language level is explicitlyset to 2. To mitigate the effect, Cython 3.0 still accepts both Pythonint
andlong
values under Python 2.x.
One potential issue you may encounter is that types liketyping.List
are now understood in annotations (where previously they were ignored)and are interpreted to meanexactlist
. This is stricter thanthe interpretation specified in PEP-484, which also allows subclasses.
To make it easier to handle cases where your interpretation of typeannotations differs from Cython’s, Cython 3 now supports setting theannotation_typing
directive on aper-class or per-function level.
C++ postincrement/postdecrement operator¶
Cython 3 differentiates between pre/post-increment and pre/post-decrementoperators (Cython 0.29 implemented both as pre(in/de)crement operator).This only has an effect when usingcython.operator.postdecrement
/cython.operator.postincrement
.When running into an error it is required to add the corresponding operator:
cdefcppclassExample:Exampleoperator++(int)Exampleoperator--(int)
Public Declarations in C++¶
Public declarations in C++ mode are exported as C++ API in Cython 3, usingextern"C++"
.This behaviour can be changed by setting the export keyword using theCYTHON_EXTERN_C
macroto allow Cython modules to be implemented in C++ but callable from C.
**
power operator¶
Cython 3 has changed the behaviour of the power operator to bemore like Python. The consequences are that
a**b
of two ints may return a floating point type,a**b
of one or more non-complex floating point numbers mayreturn a complex number.
The old behaviour can be restored by setting thecpow
compiler directive toTrue
.
Deprecation ofDEF
/IF
¶
Theconditional compilation feature has beendeprecated and should no longer be used in new code.It is expected to get removed in some future release.
Usages ofDEF
should be replaced by:
global cdef constants
global enums (C or Python)
C macros, e.g. defined inverbatim C code
the usual Python mechanisms for sharing values across modules and usages
Usages ofIF
should be replaced by:
runtime conditions and conditional Python imports (i.e. the usual Python patterns)
leaving out unused C struct field names from a Cython extern struct definition(which does not have to be complete)
redefining an extern struct type under different Cython names,with different (e.g. version/platform dependent) attributes,but with thesame cname string.
separating out optional (non-trivial) functionality into optional Cython modulesand importing/using them at need (with regular runtime Python imports)
code generation, as a last resort