Types can be used in Numba to compile functions directly with the jit function, and they can be used to declare local variables in both jit and autojit functions:
@autojit(locals=dict(array=double[:, :], scalar1=double))
def func(array):
scalar1 = array[0, 0] # scalar is declared double
scalar2 = double(array[0, 0])
Of course, declaring types in this example is unnecessary since the type inferencer knows the input type of array, and hence knows the type of array[i, j] to be the dtype of array.
Note
Type declarations or casts can be useful in cases where the type inferencer doesn’t know the type, or if you want to override the type inferencer’s rules (e.g. force 32-bit floating point precision).
Cases where the type inferencer doesn’t know the type is often when you call a Python function or method that is not a numba function and numba doesn’t otherwise recognize.
Numba allows you to obtain the type of a expression or variable through the typeof function in a Numba function. This type can then be used for instance to cast other values:
type = numba.typeof(x + y)
value = type(value)
When used outside of a Numba function, it returns the type the type inferencer would infer for that value:
>>> numba.typeof(1.0)
double
>>> numba.typeof(cmath.sqrt(-1))
complex128
Variables declared in the locals dict have a single type throughout the entire function. However, any variable not declared in locals can assume different types, just like in Python:
@autojit
def variable_ressign(arg):
arg = 1.0
arg = "hello"
arg = object()
var = arg
var = "world"
However, there are some restrictions, namely that variables must have a unifyable type at control flow merge points. For example, the following code will not compile:
@autojit
def incompatible_types(arg):
if arg > 10:
x = "hello"
else:
x = arg
return x # ERROR! Inconsistent type for x!
This code is invalid because strings and integers are not compatible. However, if we do not read x after the if block, the code will compile fine, since it does not need to unify the type:
@autojit
def compatible_types(arg):
if arg > 10:
x = "hello"
else:
x = arg
x = func()
return x
The same goes for loop carried dependencies and variables escaping loops, e.g.:
@autojit
def incompatible_types2(N):
x = "hello"
for i in range(N):
print x # ERROR! Inconsistent type for x!
x = i
return x
@autojit
def incompatible_types3(N):
x = "hello"
for i in range(N):
x = i
print x
return x # ERROR! Inconsistent type for x if N <= 0
The numba type system goes far beyond the simple scalars (e.g. ushort) and arrays (e.g. float32[:, :]) that we had previously covered. We can also define structs, pointers, functions and strings.
Structs can be either aligned or unaligned (packed). Aligned structs are the recommended default. Structs can be ordered, in case we need to interface with non-numba code or NumPy record arrays:
>>> import numba
>>> numba.struct([('first_field', double), ('second_field', float_)], name='MyStruct')
struct MyStruct { double first_field, float second_field }
The name argument is optional, but useful for debugging purposes and code clarity. An unordered struct can be created using keyword arguments:
>>> numba.struct(first_field=double, second_field=float_)
struct { double first_field, float second_field }
Unordered structs order their fields by the size of their data type, and secondarily on the field name. Since sizeof(double) > sizeof(float), first_field precedes second_field in the struct.
Inferface:
Each type has a pointer method that allows one to create a pointer type with that type as the base type:
>>> double.pointer()
double *
>>> numba.struct(first_field=double, second_field=float_).pointer()
struct { double first_field, float second_field } *
Pointer support is still somewhat immature, but in the future it is likely ctypes and CFFI pointers will be supported, and possibly pointers returned by Cython cdef functions or methods. Currently pointers can be obtained from integers by using the Py_uintptr_t type, which is an integer large enough to store any pointer:
voidp = void.pointer()
@autojit
def test_compare_null_attribute():
return voidp(Py_uintptr_t(0)) == numba.NULL
Note how we declare the type void * outside the function, since numba does not yet recognize void.pointer() inside a numba function as the type which it constitutes (it is also valid to pass voidp in as an argument to the function).
Note also how we use numba.NULL, which represents the C NULL pointer, and may be compared to a pointer of any type.
Note
Type declarations inside numba functions itself is still immature, but at any time can types be passed into numba functions, or accessed as module attributes or globals.
Inferface:
As we have already seen, functions can be easily specified by calling types:
>>> void(double)
void (*)(double)
Function types can also be created through the FunctionType class exposed in the numba namespace. For instance, this allows you to omit a return type, and to have the type inferencer infer the return type automatically:
>>> numba.FunctionType(return_type=None, args=[], name="foo")
None (*foo)()
>>> numba.FunctionType(return_type=void, args=[], name="foo", is_vararg=True)
void (*foo )(...)
Inferface:
Strings may be specified through the c_string_type type, a name which is subject to change in the future. This does not handle unicode, and is equivalent to char *:
>>> c_string_type
const char *
NumPy datetime and timedelta types are supported with numba.datetime and numba.timedelta types. Internally a NumPy datetime or timedelta object is converted to a struct type with a timestamp/timedelta field and a datetime units field, so datetime and timedelta objects can be used in nopython mode.
NumPy datetime and timedelta scalars and arrays can be passed to a numba function. Datetime components can also be accessed:
@numba.jit(void(numba.datetime), nopython=True)
def foo(x):
year = date.year
foo(numpy.datetime64('2014-01-01'))
New datetime and timedelta objects can be created inside a numba function and returned.
@numba.autojit(nopython=True)
def foo():
date = numpy.datetime64('2014-01-01')
return date
Currently a datetime can be subtracted from another datetime to get a timedelta, and a timedelta can be added to or subtracted from a datetime to get a new datetime:
@numba.jit(numba.timedelta(numba.datetime, numba.datetime), nopython=True)
def foo1(date1, date2):
return date1 - date2
@numba.jit(numba.datetime(numba.datetime, numba.timedelta), nopython=True)
def foo2(date, delta):
return date + delta
Arrays of datetimes and timedeltas can be passed in and returned from a numba function, and indexed within a numba function. When a datetime array type is specified, the datetime units must also be specified (datetime units are the same as NumPy datetime64 units):
@numba.jit(numba.datetime(numba.datetime(units='M')[:]))
def foo(datetimes):
return datetimes[0]
Templates allow the user to deal with types in a more abstract manner, which is useful when concrete types are not available at the time of specification. This can be used in conjuction with the autojit decorator, which determines the types based on the argument input values. For example, this allows one to access the base type of a pointer, or the dtype of an array to declare variable types or perform casts:
T = numba.template("T") # the name argument is optional
@autojit(T(T[:, :]), locals=dict(scalar=T))
def test_templates(array):
scalar = array[0, 0]
return scalar
This specifies that the function takes a 2D array of some dtype T, and returns a value of type T. The local variable scalar also assumes type T. In this example we could just as well have relied on the type inferencer, but we have gained a constraint on the input type array, namely that it is a 2D array.
We can loosen the constaint a bit, and for instance allow any N-dimensional array to be passed in:
T = numba.template()
dtype = T.dtype
@autojit(dtype(T), locals=dict(scalar=dtype))
def test_template_generic(array):
scalar = array[(0,) * array.ndim]
return scalar
We can do a similar thing with pointers by accessing the base_type attribute, or with struct fields by indexing fielddict. E.g. we could write:
pointer_type = T2.pointer()
struct_type = numba.struct(array=T1[:], pointer=pointer_type, func=T1(pointer_type, T3))
@autojit(void(struct_type))
def process_struct(struct):
arg = T3(0)
array[0] = struct.func(struct.pointer, arg)
as:
cast_type = T.fielddict["func"].args[1] # Get at the T3 type in the example above
@autojit(void(T))
def process_struct(struct):
arg = cast_type(0)
array[0] = struct.func(struct.pointer, arg)