SciJitClass
Callable instances for numba jitclasses. Originally created for SciJIT.
Reference every feature · Guide the long form, with measurements
@scijitclass([('a', float64)])
class Scale:
def __init__(self, a):
self.a = a
def ev_one(self, x): # stands in for __call__, for a scalar argument
return self.a * x
def ev(self, xs): # stands in for __call__, for an array argument
return self.a * xs
s = Scale(2.0)
s(3.0) # 6.0
Calling a jitclass instance
numba compiles a function marked @njit to
machine code, so running that function in a loop does not go through the
interpreter. Compiled code accepts a fixed set of types, and an ordinary Python
class is not one of them. numba offers @jitclass instead: it compiles a class,
and its instances work inside @njit. Every field, meaning every value an
instance stores, has to be declared with its type in the decorator.
@jitclass has no equivalent of __call__.
scijitclass registers __call__ through numba's public extension API. The
compiler picks the method from the argument types while the calling function
compiles.
It also registers a registered instance as a compile-time constant, so one held
in a module-level global or closed over by a compiled function is frozen into the
compiled code, which a plain jitclass cannot be. This is what lets an instance be
read inside the C callback a routine such as scipy.optimize.fsolve compiles from
a Python function. See docs/GUIDE.md.
Install
pip install scijitclass
From a clone:
pip install .
Requires Python 3.10 or later, numba >= 0.66 and numpy. Every number quoted
here and in docs/ was measured on numba 0.66.0, numpy 2.4.6, Python 3.14.6.
Timings vary with the machine.
Simple example
import numpy as np
from numba import njit, float64
from scijitclass import scijitclass
@scijitclass([('a', float64)]) # field types, exactly as jitclass takes them
class Scale:
def __init__(self, a):
self.a = a
# Scale defines no __call__. These two are what a call forwards to,
# picked from the type of the argument:
def ev_one(self, x): # s(3.0) one scalar
return self.a * x
def ev(self, xs): # s(array) one array
return self.a * xs
s = Scale(2.0)
s(3.0) # 6.0
s(np.array([1.0, 2.0])) # array([2., 4.])
@njit
def use(o, x):
return o(x) # the same call, inside compiled code
use(s, 3.0) # 6.0
# A jitclass constructor cannot carry a default inside @njit. A plain @njit
# function can, so the default goes on a one-line factory instead.
@njit
def scale_default(a=2.0):
return Scale(a)
sd = scale_default() # a = 2.0, from the default
sd(3.0) # 6.0
@njit
def shifted(x, y):
o = scale_default(3.0) # instance built inside compiled code with non-default value
return o(x) + y
shifted(3, 5.2) # 14.2 -> ev_one
shifted(3, np.array([5.2, 2.5])) # array([14.2, 11.5]) -> ev_one
shifted(np.array([3.0, 1.5]), np.array([5.2, 2.5])) # array([14.2, 7. ]) -> ev
An ordinary Python class defines __call__ and branches inside it on the
argument. A jitclass cannot define __call__, so scijitclass puts the branch
outside the class, in a table. Each entry names a method and the calls that
method accepts.
With no dispatch= argument the table comes from the two method names, and
reads:
from scijitclass import all_scalar, first_array
@scijitclass([('a', float64)], dispatch=[('ev', first_array),
('ev_one', all_scalar)])
Adding more call argument types
A table can name any method and accept any argument type numba can type. One object, four call shapes; the arrows show which method each call runs.
from scijitclass import scijitclass, sig, Scalar, Array, String
@scijitclass([('a', float64)], dispatch=[ # what line(...) forwards to
('total', sig()), # line() no arguments
('at', sig(Scalar)), # line(2.0) one number
('over', sig(Array)), # line(array) one array
('by_name', sig(String)), # line("slope") one string
])
class Line:
def __init__(self, a):
self.a = a
def total(self):
return self.a * 100.0
def at(self, x):
return self.a * x
def over(self, xs):
return self.a * xs
def by_name(self, what):
return self.a if what == "slope" else -1.0
line = Line(2.0)
line() # 200.0 -> total()
line(3.0) # 6.0 -> at()
line(np.array([1.0, 2.0])) # array([2., 4.]) -> over()
line("slope") # 2.0 -> by_name()
Strings, tuples and other jitclass instances select a method the same way.
Usage warnings
Two guards accepting the same call is an error. A guard is the rule saying
which calls a method takes. An entry written without one accepts everything
numba accepts, and numba is permissive: a method
written for an array usually type-checks for a number too. Scalar also covers
Integer and Float, so sig(Scalar, Scalar) and sig(Float, Integer) both
accept (2.0, 3). That overlap raises AmbiguousDispatch, and the message
names both methods.
A dispatch table replaces the ev_one / ev default; it does not extend
it. A class defining ev_one that passes dispatch=[('named', ...)] accepts
a string and rejects a number. ev_one stays an ordinary method, and obj(...)
cannot reach it.
A Python list is not an array. numba gives a Python list its own type, a
reflected list, which is not a numpy array and which no array guard admits.
obj([1.0, 2.0]) raises
TypeError: Scale has no method accepting (reflected list(float64)<iv=None>).
Pass np.array(...).
Constructor defaults apply from Python only. A defaulted argument works in
the interpreter and raises inside @njit, where every argument is required.
Plain jitclasses behave the same way. An @njit factory carries a default in,
as scale_default does above.
cache=True never hits on a function taking a jitclass. numba accepts the
flag and recompiles every session.
A call from Python costs about 2.5 microseconds: argument conversion, then
crossing the box numba wraps an instance in. Inside @njit the compiler picks
the method while the calling function compiles, and the call costs what naming
the method costs. Build the
object once and use it in compiled code. A plain Python class is faster if the
object never gets there and the method body is small.
Documentation
docs/REFERENCE.md lists every feature: field types, how
a call is declared, the guard catalogue, introspection, costs and restrictions.
Read this to look something up.
docs/GUIDE.md is the long form: what a jitclass is, working
with several objects at once, and the measurements behind the numbers. Read
this to understand why something behaves as it does.
The four scripts in examples/ run in order and print what they measure.
Origin
Written for SciJIT, a package of
SciPy-equivalent routines callable inside numba @njit code. Its spline,
interpolator and distribution classes are jitclasses, and SciPy spells their
evaluation obj(x).
Status
Version 0.1.7
Created with the help of Claude.
Not affiliated with the numba project.
scijitclass is a separate package that uses numba's public extension API.
Licensed under BSD-3-Clause. See LICENSE.
Metadata
Release files for scijitclass 0.1.7
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| scijitclass-0.1.7.tar.gz | 42.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| scijitclass-0.1.7-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 63.3 kB
Release files / scijitclass-0.1.7.tar.gz
| Download URL | scijitclass-0.1.7.tar.gz |
|---|---|
| Size | 42.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e66d8513569e6688904b94a53f482b0c3b6e1a9ad70141a5dc0469ac966e4354
|
|
BLAKE2b-256 checksum How to use checksums |
3ab5ba6333b7787b4866fdfc9681d7def93f77d2c8d60c5d02ea6e8e1a0fb54a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 22, 2026.
Transparency logRelease files / scijitclass-0.1.7-py3-none-any.whl
| Download URL | scijitclass-0.1.7-py3-none-any.whl |
|---|---|
| Size | 21.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
70d6a1ed66224e1978aaec9539560f7787b013305dba92894ea35938b0109191
|
|
BLAKE2b-256 checksum How to use checksums |
35939c389abb79bd35c19a187791969cec10215159e3d0ef85f961ba0411d3ec
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 22, 2026.
Transparency log