Troubleshooting

Where the language is “messy”

By necessity, Cython is a slightly odd mix of the resolved-at-run-time, dynamic behaviour of Python, and the statically-defined, resolved at compile-time behaviour of C. These don’t always combine perfectly, and the places that often cause confusion are often the places they meet.

As example, for a cdef class, Cython is able to access cdef attributes directly (as a simple C lookup). However, if the direct attribute lookup “misses” then Cython doesn’t produce an error message - instead it assumes that it will be able to resolve that attribute through the standard Python “string lookup from a dictionary” mechanism at runtime. The two mechanisms are quite different in how they work and what they can return (the Python mechanism can only return Python objects, while the the direct lookup can return largely any C type).

Much the same can occur when a name is imported rather “cimported” - Cython does not know where the name comes from so treats it as a regular Python object.

This silent-fallback to Python behaviour is often a source of confusion. In the best case it gives the same overall behaviour but slightly slower (for example calling a cpdef function through the Python mechanism rather than directly to C). Often it just causes an AttributeError exception at runtime. Very occasionally it might do something quite different - invoke a Python method with the same name as a cdef method, or cause a convert from a C++ container to a Python one.

This kind of dual-layered behaviour probably isn’t how one would design a language from scratch, but is needed for Cython’s goals for being Python compatible and allowing C types to be used fairly seamlessly.

AttributeErrors

Untyped objects

A common reason to get AttributeErrors is that Cython does not know the type of your object:

cdef class Counter:
    cdef int count_so_far

    ...

The attribute count_so_far is only accessible from Cython code, and Cython accesses it through a direct lookup into the C struct that it defines for Counter (i.e. it’s really quick!). Now try run the following Cython code on a pair of Counter objects:

def bigger_count(c1, c2):
    return c1.count_so_far < c2.count_so_far

This will give an AttributeError because Cython does not know the types of c1 and c2. Typing them as Counter c1 and Counter c2 fixes the problem:

def bigger_count(c1, c2):
    return c1.count_so_far < c2.count_so_far

A common variation of the same problem happens for global objects:

def count_something():
    c = Counter()

    # code goes here!!!

    print(c.count_so_far)  # works

global_count = Counter()
print(global_count.count_so_far)  # AttributeError!

Within a function Cython usually manages to infer the type. So it knows that c is a Counter even though you have not told it. However the same doesn’t apply at global/module scope. Here there’s a strong assumption that you want objects to be exposed as Python attributes of the module (and remember that Python attributes could be modified from elsewhere…), so Cython essentially disables all type-inference. Therefore it doesn’t know the type of global_count.

Writing into extension types

AttributeError``s can also happen when writing into a ``cdef class, commonly in __init__:

cdef class Company:
    def __init__(self, staff):
        self.staff = staff  # AttributeError!

Unlike a regular class, cdef class has a fixed list of attributes that you can write to and you need to declare them explicitly. For example:

cdef class Company:
   cdef list staff
   # ...

(use cdef staff or cdef object staff if you don’t want to specify a type). If you do want the ability to add arbitrary attributes then you can add a __dict__ member:

cdef class Company:
   cdef dict __dict__
   def __init__(self, staff):
       self.staff = staff

This gives extra flexibility, but loses some of the performance benefits of using an extension type. It also adds restrictions to inheritance.

Extension type class attributes vs instance attributes

A common pattern in Python (used a lot within the Cython code-base itself) is to use instance attributes that shadow class attributes:

class Email:
    message = "hello"  # sensible default

    def actually_I_really_dislike_this_person(self):
        self.message = "go away!"

On access to message Python first looks up the instance dictionary to see if it has a value for message and if that fails looks up the class dictionary to get the default value. The advantages are

  • it provides an easy sensible default,

  • it potentially saves a bit of memory by not populating the instance dictionary if not necessary (although modern versions of Python are pretty good at sharing keys for common attributes between instances),

  • it saves a bit of time reference counting (vs if you initialized the defaults in the constructor),

Cython extension types don’t support this pattern. You should just set the defaults in the constructor. If you don’t set defaults for a cdef attribute then they’ll be set to an “empty” value (None for Python object attributes).