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.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file scijitclass-0.1.7.tar.gz.
File metadata
- Download URL: scijitclass-0.1.7.tar.gz
- Upload date:
- Size: 42.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e66d8513569e6688904b94a53f482b0c3b6e1a9ad70141a5dc0469ac966e4354
|
|
| MD5 |
67ca4bf7d993c1b7726c064d37f4b52a
|
|
| BLAKE2b-256 |
3ab5ba6333b7787b4866fdfc9681d7def93f77d2c8d60c5d02ea6e8e1a0fb54a
|
Provenance
The following attestation bundles were made for scijitclass-0.1.7.tar.gz:
Publisher:
publish.yml on Shmuel-Gilbaum/SciJITClass
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
scijitclass-0.1.7.tar.gz -
Subject digest:
e66d8513569e6688904b94a53f482b0c3b6e1a9ad70141a5dc0469ac966e4354 - Sigstore transparency entry: 2568118711
- Sigstore integration time:
-
Permalink:
Shmuel-Gilbaum/SciJITClass@1a9a230cc86631806ae06001f88e231082e736f9 -
Branch / Tag:
refs/tags/v0.1.7 - Owner: https://github.com/Shmuel-Gilbaum
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1a9a230cc86631806ae06001f88e231082e736f9 -
Trigger Event:
release
-
Statement type:
File details
Details for the file scijitclass-0.1.7-py3-none-any.whl.
File metadata
- Download URL: scijitclass-0.1.7-py3-none-any.whl
- Upload date:
- Size: 21.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
70d6a1ed66224e1978aaec9539560f7787b013305dba92894ea35938b0109191
|
|
| MD5 |
79b8663a81a6ca599a5597edf15ca791
|
|
| BLAKE2b-256 |
35939c389abb79bd35c19a187791969cec10215159e3d0ef85f961ba0411d3ec
|
Provenance
The following attestation bundles were made for scijitclass-0.1.7-py3-none-any.whl:
Publisher:
publish.yml on Shmuel-Gilbaum/SciJITClass
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
scijitclass-0.1.7-py3-none-any.whl -
Subject digest:
70d6a1ed66224e1978aaec9539560f7787b013305dba92894ea35938b0109191 - Sigstore transparency entry: 2568118733
- Sigstore integration time:
-
Permalink:
Shmuel-Gilbaum/SciJITClass@1a9a230cc86631806ae06001f88e231082e736f9 -
Branch / Tag:
refs/tags/v0.1.7 - Owner: https://github.com/Shmuel-Gilbaum
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@1a9a230cc86631806ae06001f88e231082e736f9 -
Trigger Event:
release
-
Statement type: