Circular imports referencing

I have a python library made out of various classes that I would like to speed up using numba. There is some circular imports which I am able to resolve easily using from __future__ import annotations however I run into a problem when trying to use Numba

See if I can make up a simple example

 class triangle:
          def __init__(self, e1, e2, e3):
                 self.e1 = Edge(1)
                 self.e2 = Edge(2)
                 self.e3 = Edge(3)

class Edge:
          def __init__(self, value):
                self.value = value
                self.triangle = None

In this case, an edge may have a triangle associated to it or not. How would I specify both classes (i.e. define spec so that I could use @jitclass(spec) decorator?


Hey @MLLO ,

Can you initialize the classes without circular reference i.e. by using an id in both classes referencing each other?
Something like this:

from numba import int32, optional
from numba.experimental import jitclass

edge_spec = [
    ('value', int32),
    ('triangle_id', optional(int32)),  # Use optional for triangle reference

class Edge:
    def __init__(self,
                 value: int,
                 triangle_id: int = None):
        self.value = value
        self.triangle_id = triangle_id

# Use typedEdge to refer to the type of the Edge class within the triangle_spec.
typedEdge = Edge.class_type.instance_type

triangle_spec = [
    ('triangle_id', int32),
    ('e1', typedEdge),
    ('e2', typedEdge),
    ('e3', typedEdge),

class Triangle:
    def __init__(self,
                 triangle_id: int,
                 e1: Edge,
                 e2: Edge,
                 e3: Edge):
        self.triangle_id = triangle_id
        # update edges
        e1.triangle_id = self.triangle_id
        e2.triangle_id = self.triangle_id
        e3.triangle_id = self.triangle_id
        self.e1 = e1
        self.e2 = e2
        self.e3 = e3

# Initialize Edges
e1=Edge(1, None)
e2=Edge(2, None)
e3=Edge(3, None)

# Initialize triangle and update edges
triangle = Triangle(1, e1, e2, e3)
print(f'edge: {triangle.e1.value}, triangle: {e1.triangle_id}')
print(f'edge: {triangle.e2.value}, triangle: {e2.triangle_id}')
print(f'edge: {triangle.e3.value}, triangle: {e3.triangle_id}')
# output:
# edge: 1, triangle: 1
# edge: 2, triangle: 1
# edge: 3, triangle: 1

Thanks @Oyibo I will need to think carefully how to do this (I offered a simple example) but it is a good suggestion! Thanks.

Would you do the same if an object is referring to itself?
Say Edge can refer to other edges???

Hey @MLLO,

I’m just a fellow user of Numba, not a maintainer.

In general, Numba is quite adept at handling objects that reference each other, especially when those references are indirect through basic types like integers or floats. Numba’s type inference capabilities can often resolve complex relationships in your code.

However, it’s important to remember that one of the primary benefits of Numba is to provide the performance of low-level languages like C or Fortran while keeping the simplicity of Python. Introducing higher complexity into your code, especially to work around circular references, might not always be the most favorable approach.
There are trade-offs between performance and code simplicity or maintainability.


circular imports are a pain, but if they surface at runtime they usually indicate something is poorly structured to begin with. For (non-numba) type annotation purposes (NOT runtime) the proper way to get around this is to use the typing.TYPE_CHECKING guard for imports only used in annotations.

However referencing your Self type, or circular type chains are quite common, and as such numba offers the deferred_type to indicate that the concrete type will only be defined later. If you search for this expression, there is a few threads discussing its use in the context of jitclasses.

Thank you @Oyibo and @Hannes your coments are very helpful. I am new to numba so I am still trying to figure out things. I have been trying to find more comprehensive tutorials on it. It seems that there are far more options than appear at a first glance in the documentation. Thanks again.

Glad to help - good luck with your work - numba can be quite fun once you poke it a little :wink:
Unfortunately, I currently don’t check in here very regularly, but don’t worry about following up in case the existing threads cannot answer your questions