In our previous video we started a very interesting discussion about the major two differences which help us to understand how cython can provide the performance boost, our last point was dynamic versus static typing where we discussed the cdef keyword, we will continue the discussion in this video.

Before proceeding further, I want to mention an important point here:

Difference between Dynamic and Static Variables

The important difference between dynamic and static variables is that static variables with C types have C semantics, which changes the behavior of assignment. It also means these variables follow C casting rules.

If you remember the example we mentioned in the previous video, where a=b, copies the integer data at b to the memory location reserved for a. This means that a and b refer to independent entities, and can evolve separately.

Inside a function, cdef statements are indented and the static variables declared are local to that function. All of these are valid uses of cdef to declare local variables inside a function. You can see an example code here, first is the normal way to define the cdef variable but in the second one we utilized the cdef block.

One another thing you should keep in mind is that Cython supports the const keyword instead of the C static keyword, because The C static keyword is used to declare a variable whose lifetime extends to the entire lifetime of a program. It is not a valid Cython keyword, so we cannot declare C static variables in Cython.

We can declare different kinds of variables that C supports like: Pointers, Stack-allocated C arrays and Function pointers etc.

Cython supports the full range of C declarations, even the cryptic tongue twisters like this one, which we can read as:


For example, to declare a function that takes a function pointer as its only argument and returns another function pointer, we could say something like this:

cdef int (*signal(int (*f)(int))(int)

Static typing with cdef is not the only way to statically type variables in Cython. It also performs automatic type inference for untyped variables inside the bodies of functions and methods. By default, it infers variable types only when doing so cannot change the semantics of the code.

Just take a look at this simple function:

def auto_infer():
    b = 2.0
    c = 3+4j
    return r 

Here Cython types the literals 1 and 3+4j and the variables a, c, and r as general Python objects. Even though these types have obvious corresponding C types, Cython conservatively assumes that the integer a may not be representable as a long type of variable in C, so types it as a Python object with Python semantics. Automatic inference is able to infer that the variable b, is type of double in C and proceeds accordingly. To the end user, it is as if b is a regular Python object, but Cython treats it as a C double for performance.

By Using the infer_types compiler directive, we can give Cython more freedom to infer types in cases that may change semantics—for example, when integer addition may result in overflow.

To enable type inference for a function, we can use the decorator form of infer_types as you can see in this example:

   def infer_dec():
            b = 2.0
            c = 3+4j
            return r

Because infer_types is enabled for infer_dec function, the variable a is typed as a C long; b is a double, as before, and both c and r are C-level complex variables.

But one thing you should consider is that: When enabling infer_types, we are taking responsibility to ensure that integer operations do not overflow and that semantics do not change from the untyped version.

Now, let’s talk about the C Pointers in Cython. We can declare the C pointers by using the cython’s syntax and semantics like these examples. You can see here: the asterisk symbol can be declared contiguous to the type or to the variable itself, although the pointerness is linked with the variable, not the type like this [ cdef int *x, *y ] one.

If we write that state in this [cdef int *x, y] way, it will declare an integer pointer x, and a nonpointer integer y, and cython will issue a warning when compiling error-prone declarations such as these. That’s how we can reference variables as pointers.

But, Dereferencing pointers in Cython is different than in C. Because the Python language already uses the *args and **kwargs syntax to allow arbitrary positional and keyword arguments and to support function argument unpacking, Cython does not support the *a syntax to dereference a C pointer. Instead, we index into the pointer at location 0 to dereference a pointer in Cython. This syntax also works to dereference a pointer in C.

For example, suppose we have a hot_pizza variable which is the double type of C and a C pointer named topping:

   cdef double hot_pizza
   cdef double *topping

We can assign hot_pizza’s address to topping using the address-of operator, & Like this: topping = &hot_pizza

We can now assign to hot_pizza through topping using our indexing- at-zero-to-dereference syntax like this:

  topping[0] = 1.618

And we can access topping’s referent the same way:

  # => 1.618

Alternatively, we can use the cython.operator.dereference function-like operator to dereference a pointer.

That’s it for now, we explore various things about cython’s cdef, how to define variable types, How automatic type inference works in cython and we also talked about the pointers in cython. I think that’s enough for this video, in our video we will understand how Statically and Dynamically Typed Variables can be mixed together to get more performance along with the ease.

Oct. 20, 2021
Web Development

More from 

Web Development


View All