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()