Hi everyone, welcome back, In our previous tutorial 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:
Static VS Dynamic 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, cdefstatements 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 below, first is the normal way to define the cdef variable but in the second one we utilized the cdef block.
def merge(j, k, l):
cdef int j
cdef int N=2000
cdef float dx, s=0.0
def merge(j, k, l):
float dx, s=0.0
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)
Don’t put too much pressure on your mind, we will explore that in more detail in our later videos of this series, so be connected and don’t forget to like this video, great!
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:
b = 2.0
c = 3+4j
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 semanticsfor 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:
b = 2.0
c = 3+4j
Because infer_types is enabled for infer_decfunction, 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_pizzathrough topping using our indexing at zero to dereference syntax like this:
topping = 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.
I hope you really enjoyed the discussion here. If you have any question regarding static and dynamically typing variables comment below. Thanks!