Basic Syntax (by example)

A simple cypclass

Let's start with a simple example:

cdef cypclass Character:
    int health

    __init__(self, int health):
        self.health = health

    int update_health(self, int amount):
        if -amount > health:
            self.health = 0
        else:
            self.health += amount
        return self.health

This is a declaration for a cypclass named Character.

It has a field named health which is of C integer type int. All Cypclass attributes must be declared statically in the body of the class. No monkey-patching allowed !

It has a method named __init__. Like in Python, this is a special method that will be called when an instance of the class is created. Notice that the def keyword is absent. That's because it is a cypclass method, not a Python method. Notice also that the health argument needs a C-style type annotation, but the self argument does not. Otherwise it looks like standard Python code.

There is a also second method named update_health. The return type is the C type int. It takes an additional argument of type int.

Inheritance

Cypclasses support simple and multiple inheritance, like Python classes.

cdef cypclass Player(Character):
    int score

    __init__(self, int health):
        self.health = health
        self.score = 0

    Player __iadd__(self, int bonus):
        self.score += bonus
        return self

Notice that the Player cypclass declares a special method __iadd__. Like in Python, this method will be transparently called when a Player object is the left-hand side of a += operation. We will see this illustrated later.

GIL freedom

Every cypclass method is implictly a nogil method. This is a Cython annotation that declares that the function can be called without holding the GIL; in return the function body cannot do anything that would require holding the GIL (except if it acquires the GIL first).

Thanks to this, a cypclass can be fully used without requiring the GIL at any point:

def main():
    cdef Player p
    with nogil:
        # Create your Player character
        # with an initial health of 10
        # and an initial score of 0
        p = Player(10)

        # Pick up a +2 bonus
        p += 2

        # Take a -1 hit to health
        p.update_health(-1)

The with nogil block guarantees that the GIL is released at the beginning of the block and that the previous state is restored at the end of the block.

The line cdef Player p serves to declare that p is a variable of type Player. Type inference should remove the need for such declarations, but it is still a work in progress: Cython is sometimes too eager to infer to the Python object type (Cython falls back on Python object as the type when it fails to infer otherwise), and such declarations are a workaround such type inference bugs.

Sometimes, wou still need to acquire the GIL in order to call a Python function. You can then use with gil block that works exactly in reverse to a with nogil block: it guarantees that the GIL is acquired at the beginning, and restores the state to the previous state at the end.

If you need the GIL in the whole body of a cypclass method, you can use the with gil annotation in the method declaration:

    void dump(self) with gil:
        print("Player Character (health: %d, score %d)" % (self.health, self.score))

You can then call such a method even without holding the GIL.

def main():
    cdef Player p
    with nogil:
        # Create your Player character
        p = Player(10)

        # [...]

        # Print the stats of your Player
        p.dump()

Full code

cdef cypclass Character:
    int health

    __init__(self, int health):
        self.health = health

    int update_health(self, int amount):
        if -amount > health:
            self.health = 0
        else:
            self.health += amount
        return self.health

cdef cypclass Player(Character):
    int score

    __init__(self, int health):
        self.health = health
        self.score = 0

    Player __iadd__(self, int bonus):
        self.score += bonus
        return self

    void dump(self) with gil:
        print("Player Character (health: %d, score: %d)" % (self.health, self.score))

def main():
    cdef Player p
    with nogil:
        # Create your Player character
        # with an initial health of 10
        # and an initial score of 0
        p = Player(10)

        # Pick up a +2 bonus
        p += 2

        # Take a -1 hit to health
        p.update_health(-1)

        # Print the stats of your Player
        p.dump()

main()