numbox.core.bindings
Overview
Loads dynamic libraries available in the Python environment, such as, libc, libm, and libsqlite3 in global symbol mode (RTLD_GLOBAL) via ctypes. This adds global symbols (including native API) exported from those libraries to the LLVM symbol table. These functions can then be invoked from the numba jitted code [1], complementing the suite of numba-supported functionality.
Analogous technique can be expanded as needed for the user custom code.
References
Bindings module conventions
Every binding in the numbox.core.bindings family (_c, _math,
_sqlite, _stdio, _errno, _strerror, _fmtio) uses
extern-symbol references via
_call_lib_func(), so the ABI dispatch is
ASLR-safe. Pointer arguments are typed as intp – the caller is
responsible for liveness, alignment, and ownership of the underlying memory.
All wrappers are decorated with proxy() –
safe to reference from a user @njit(cache=True) caller. proxy
declares the callee’s llvm_cfunc_wrapper_name as an extern in the
caller’s module and lets llvmlite’s JIT linker resolve it per process, so
cached caller IR re-resolves the function pointer correctly under ASLR.
References for individual binding semantics:
POSIX / Linux glibc: man7.org
macOS Darwin: Apple Open Source Libc
Windows UCRT: Microsoft Learn
ABI dispatch
LLVM’s JIT treats ABI lowering as a frontend responsibility — it won’t insert the right calling convention
for struct args/returns by itself. numbox.core.bindings.call._call_lib_func dispatches per platform and
per struct shape, using primitives from numbox.core.bindings.abi (platform identification via
_current_platform, struct-shape classification via _classify, struct-size measurement via
_struct_bytes). The two ABI families that matter:
Windows x64 — passes aggregates >8 bytes via caller-allocated pointers and returns them via
sret; sizes 1/2/4/8 go directly in registers.SysV x86-64 / AAPCS64 — pass and return ≤16-byte aggregates directly in GP registers; on SysV x86-64, >16-byte by-value args use a
byval+optnone+noinlineidiom so the LLVM optimizer doesn’t elide the caller-side stack copy before the callee reads it.
References:
Stdio handles, errno, and thread-safe strerror
Stdio handles. stdout(), stderr(), and stdin() are exposed as JIT-callable functions
rather than module-level Python constants because the C library’s stdio FILE * values can be either
data symbols or accessor functions:
Linux (glibc and musl) — data symbols (
stdout,stderr,stdinglobal variables, declared in <stdio.h>)macOS Darwin — data symbols (
__stdoutp,__stderrp,__stdinp— what the libc headers’stdout/stderr/stdinmacros expand to per Apple’s _stdio.h)Windows — accessor function (__acrt_iob_func(0|1|2)); UCRT-only (Windows 10+ / VS 2015+)
Both shapes are wrapped behind a uniform () -> intp interface using extern-symbol references in LLVM IR —
never literal addresses — so that cache=True remains correct under ASLR: the address is resolved at
JIT link time on each run rather than being baked into the cached object.
Example — write to stderr from inside @njit:
from numba import njit
from numbox.core.bindings import stderr, fputs, fflush
@njit(cache=True)
def log_to_stderr(msg_p):
fputs(msg_p, stderr())
fflush(stderr())
errno. errno_get() and errno_set(v) reach the per-thread errno location on every call via the
platform’s accessor function (__errno_location on Linux glibc and musl,
__error
on Darwin, _errno
on Windows UCRT). This makes the wrappers correct under @njit(parallel=True): each prange worker
sees its own thread’s errno. A Python caller observes errno set inside a normal @njit function (same
OS thread), but not errno set inside a prange worker (different OS thread).
Example — read and report errno after a syscall-style binding:
from numba import njit
from numbox.core.bindings import errno_get, errno_set
@njit(cache=True)
def clear_then_call_and_report(do_work):
errno_set(0)
result = do_work()
return result, errno_get()
Thread-safe strerror. strerror_safe(errnum, buf, buflen) writes the error message into a
caller-supplied buffer, returning 0 on success and a positive errno code on failure. The underlying
symbol is selected at lowering time:
glibc Linux — __xpg_strerror_r (POSIX XSI form, present on glibc 2.3.4+ which shipped in 2004)
musl Linux — also
__xpg_strerror_r, exported as a weak alias to musl’s ownstrerror_r(which is itself the POSIX form; musl never shipped the GNU char-pointer form)macOS Darwin — strerror_r (POSIX form)
Windows — strerror_s with reordered args (buffer, size, errnum)
Other Linux libcs are not supported: on glibc the plain strerror_r symbol is the
GNU form (returns char *) and would
not match the POSIX-shaped IR this module generates. The Linux selector unconditionally picks
__xpg_strerror_r, which resolves correctly on both glibc and musl (the musl path goes through the
weak alias). A strerror_r fallback remains in the selector as defense-in-depth in case a future
libc drops __xpg_strerror_r, but the fallback is currently unreachable on every supported libc.
The Linux selector logic is verified by an IR-inspection test (Linux-only) that monkeypatches
ll.address_of_symbol to drive the fallback branch. The musl symbol layout (strerror_r
and __xpg_strerror_r both exported, both resolving to the same address via musl’s weak
alias) is a runtime invariant the binding depends on; an Alpine-container CI canary that pins
the assumption via nm -D is a straightforward way to verify it.
Example — render the message for ENOENT (errno 2 on POSIX) into a buffer:
import errno
import numpy as np
from numba import njit
from numbox.core.bindings import strerror_safe
from numbox.utils.lowlevel import array_data_p
@njit(cache=True)
def explain(errnum, buf):
rc = strerror_safe(errnum, array_data_p(buf), buf.size)
return rc
buf = np.zeros(128, dtype=np.uint8)
rc = explain(errno.ENOENT, buf)
msg = bytes(buf[:buf.tolist().index(0)]).decode()
# rc == 0; msg is the (locale-dependent) string for ENOENT
Variadic formatted I/O — printf / fprintf / snprintf / sscanf
printf, fprintf, snprintf (and sscanf for the parsing
direction) are dual-mode bindings: each is a regular Python function with
an @overload registration that routes to a private @intrinsic
codegen path when called inside @njit. Same source runs unchanged
in plain Python AND under @njit, matching numba’s own convention
for builtins like print and range.
Call convention — C-like *args after the format string, no tuple
wrapper at the call site:
printf("x = %d, ratio = %.3f\n", n, ratio)
fprintf(stderr(), "warning: %s\n", msg)
snprintf(array_data_p(buf), buf.size, "[%d:%d]", lo, hi)
sscanf(buf_p, "%d %lf", array_data_p(n_out), array_data_p(x_out))
printf("no args here\n")
The internal @intrinsic still uses tuple-as-args because numba’s
@intrinsic typing function doesn’t accept Python-level *args;
the @overload impl bundles the user’s *args into a tuple before
calling the intrinsic.
Dual-mode in action:
from numba import njit
from numbox.core.bindings import printf, fflush, stdout
def debug_kernel(x, label):
printf("step %d: %s\n", x, label)
fflush(stdout())
return x * 2
debug_kernel(7, "before") # pure Python: writes via sys.stdout
njit(debug_kernel)(7, "before") # jitted: writes via libc printf
# NUMBA_DISABLE_JIT=1 mode: also works (decorator becomes no-op)
Format string must be a literal in @njit. Required to embed it as an
IR global constant at typing time. A runtime-built unicode raises a
clean TypingError. In pure-Python mode the format string can be any
str.
Format string encoding: UTF-8. Non-ASCII codepoints in the literal
are encoded as UTF-8 byte sequences and embedded into the IR global.
printf treats every non-% byte as opaque pass-through, so the bytes
flow through libc to stdout / FILE * / the snprintf buffer unmodified.
Modern terminals, files, and Windows 10+ consoles all expect UTF-8.
Note
%-Nswidth is byte-counted by printf in every libc, so non-ASCII output won’t right-pad to a codepoint count. That’s printf’s contract. Pad in numba-side string formatting (f"{s:<10}") before passing through%sif codepoint-counted widths matter.
String args auto-conversion. When the @overload sees an arg whose
type is unicode_type or Literal[str], it auto-wraps the arg with
get_unicode_data_p so libc’s %s receives a NUL-terminated C
string. User code passes a raw Python str — no get_unicode_data_p
ceremony at the call site:
printf("hello %s\n", "world") # works in both modes; no get_unicode_data_p
The pre-conversion form (printf("hello %s\n", get_unicode_data_p(s)))
is still supported for backward compat or when the user has a
precomputed pointer.
Integer promotion to 64-bit. The @njit impl widens every integer
variadic arg to 64-bit before the libc call (sext for signed, zext
for unsigned, bool``→``int64 via zext, float32``→``float64
via fpext). This diverges from strict C ABI (C only promotes
sub-int-width values) but makes %lld against int32 work in
@njit, matching pure-Python’s % which ignores length modifiers and
uses the value’s natural width. The cost is one LLVM widening
instruction per arg — free at runtime.
Unsupported types raise ``TypingError`` at compile time — numpy
arrays (Array), complex numbers (Complex), tuples
(UniTuple/Tuple), records, and anything else outside the table
above. Without that guard, an aggregate LLVM value would flow straight
into libc’s variadic call and the printf would read scrambled bytes
from neighboring stack slots — silent miscompilation rather than a
clean error. Pass the field you want explicitly (arr[0], c.real,
t[0]).
Pure-Python format-spec compatibility. Python’s str.__mod__ is
printf-derived but rejects C length modifiers (%lld, %ld,
%lf, %hd, etc.) with ValueError. The pure-Python impl
strips length modifiers via a regex before formatting (%lld``→``%d,
%.3lf``→``%.3f, etc.) so the same format string works in both modes.
Warning
%ldis the most common cross-platform footgun. On LP64 (Linux, macOS)longis 8 bytes; on Win64 (LLP64) it’s 4 bytes. In @njit on Win64,printf("%ld", int64_val)truncates the high 32 bits. Pure-Python mode hides this because Python’s%ignoresl. Prefer%lld+int64for portable 8-byte width — and test on Windows if you have any%ldin your format strings.
``snprintf`` truncation rc on Windows diverges from C99/POSIX.
Linux/macOS @njit and pure-Python all return the would-have-written
count (excluding NUL); Windows @njit targets MSVCRT _snprintf which
returns -1 on truncation and doesn’t guarantee NUL-termination.
The portable check (rc < 0) or (rc >= size) works on every
platform / mode. (UCRT’s C99-compliant snprintf is a header-only
inline over __stdio_common_vsnprintf and isn’t directly linkable
in the simple C99 calling shape — declaring i32 @snprintf(...) in
LLVM IR and letting the JIT linker resolve it crashes with an access
violation on Windows.)
``fprintf`` to non-stdio ``FILE *`` in pure Python. The Python impl
caches the addresses of stdout() / stderr() / stdin() at
first use and routes those handles to the corresponding sys.*
streams. fopen-returned FILE * values can’t be dereferenced from
Python without ctypes, so they raise a clear RuntimeError in
pure-Python mode (use open() + f.write() for Python-side file
I/O, or wrap the call in @njit).
``sscanf`` is @njit-only. Pure-Python parsing is better served by
int(), float(), or re. Calling sscanf from pure Python
raises NotImplementedError with a pointer at the alternatives.
Stdout buffering. stdout is line-buffered to a terminal and
block-buffered when redirected. The pure-Python impl auto-flushes
sys.stdout after each printf to mirror the line-buffer-to-tty
behavior; @njit users should call fflush(stdout()) explicitly when
output needs to appear immediately (e.g. before a long-running compute
or under pytest’s capfd).
Caching. @njit(cache=True) callers cache cleanly across
processes: each call site emits a direct extern reference to the libc
symbol and a deterministic format-string global constant. The JIT
linker resolves the libc symbol per-process, so the cached IR is
ASLR-safe. No @proxy indirection needed — the variadic intrinsics
inline the call directly into the caller.
Example — log to stderr with fprintf(3), dual-mode:
from numba import njit
from numbox.core.bindings import fprintf, fflush, stderr
def warn(code, msg):
fprintf(stderr(), "warning code=%d: %s\n", code, msg)
fflush(stderr())
warn(7, "disk getting full") # plain Python
njit(warn)(7, "disk getting full") # jitted — identical output
Example — format into a buffer with snprintf(3), detect truncation, decode:
import numpy as np
from numba import njit
from numbox.core.bindings import snprintf
from numbox.utils.lowlevel import array_data_p
def fmt_range(lo, hi, buf):
return snprintf(array_data_p(buf), buf.size, "[%d:%d]", lo, hi)
buf = np.zeros(64, dtype=np.uint8)
n = njit(fmt_range)(7, 11, buf)
# Portable truncation check (works on Linux/macOS C99 snprintf
# *and* Windows MSVCRT _snprintf — see snprintf docstring):
truncated = (n < 0) or (n >= buf.size)
if not truncated:
msg = bytes(buf[:n]).decode() # "[7:11]"
Warning
snprintf truncation semantics diverge on Windows. POSIX / C99
snprintf returns the would-have-written count (excluding NUL) and
always NUL-terminates the buffer when size > 0. The Windows
binding targets MSVCRT’s _snprintf
— that returns -1 on truncation and does NOT guarantee
NUL-termination of the buffer. We attempted to bypass this by
declaring UCRT’s C99 snprintf directly, but the call crashed with
an access violation: UCRT exports a header-inline wrapper whose
in-process shape does not match the naive declare i32 @snprintf(...)
LLVM declaration. The portable check
(rc < 0) or (rc >= size) works on every platform.
Parsing direction: `sscanf <https://man7.org/linux/man-pages/man3/sscanf.3.html>`_. The inverse of the printf family: parse fields from a NUL-terminated input buffer into caller-supplied output pointers. Shape differs from the writers:
bufis anintppointing at the input bytes (e.g. fromget_unicode_data_p).Variadic
*argsareintpoutput pointers — each one points at writable storage that sscanf fills based on the corresponding format specifier. Typically obtained viaarray_data_pof a 1-element numpy array of the right dtype.Returns the count of items successfully assigned (
int32), or-1(EOF) on input failure before the first conversion.
Unlike printf-family, sscanf is @njit-only — pure-Python calls
raise NotImplementedError pointing the user at Python’s int() /
float() / re for native parsing. There is no default-argument
promotion (pointers don’t promote) and no string auto-conversion
(args are output pointers, not values). The binding validates only that
every variadic arg has type intp, so you can’t accidentally pass an
integer value where a pointer is expected. The pointed-to storage must
still be the right size for the format spec — the binding cannot check
that:
Format spec |
Required output points at |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
accepted in |
The %ld row is the most common cross-platform footgun and the
project’s own CLAUDE.md flags it explicitly. Prefer %lld with
an int64 output slot when you want a portable 8-byte width.
Example — parse a “<int> <double>” pair into typed numpy slots:
import numpy as np
from numba import njit
from numbox.core.bindings import sscanf
from numbox.utils.lowlevel import array_data_p, get_unicode_data_p
@njit(cache=True)
def parse_pair(text_p, n_out, x_out):
return sscanf(text_p, "%d %lf",
array_data_p(n_out), array_data_p(x_out))
n_out = np.zeros(1, dtype=np.int32)
x_out = np.zeros(1, dtype=np.float64)
rc = parse_pair(get_unicode_data_p("42 3.14"), n_out, x_out)
# rc == 2; n_out[0] == 42; x_out[0] == 3.14
Modules
numbox.core.bindings.abi
ABI primitives for numba bindings: platform / struct classification.
numbox.core.bindings._c
libc wrappers callable from numba @njit code.
See “Bindings module conventions” in docs/numbox.core.bindings.rst
for the ABI-safety, @proxy caching, and reference-source conventions
shared across all binding modules.
numbox.core.bindings._errno
numbox.core.bindings._stdio
Stdio handles (stdout/stderr/stdin) callable from @njit code.
Uses extern-symbol references in LLVM IR so cache=True remains correct
under ASLR. Linux and macOS expose the handles as data symbols (Linux:
stdout/stderr/stdin; macOS: __stdoutp/__stderrp/
__stdinp — what the libc headers’ stdio macros expand to). Windows
exposes them via an accessor function (__acrt_iob_func).
Windows requires UCRT (Universal C Runtime), bundled with Windows 10
and later. Older Windows versions exposed FILE* via per-MSVC-version
symbols (_iob, __iob_func) and are not supported.
numbox.core.bindings._strerror
Thread-safe strerror_safe(errnum, buf, buflen) callable from @njit.
Supported libcs (other Linux libcs are not supported):
glibc — __xpg_strerror_r (POSIX XSI form, present on glibc 2.3.4+ which shipped in 2004)
musl — also
__xpg_strerror_r: musl declaresstrerror_ras the POSIX XSI form and exports__xpg_strerror_ras a weak alias to the same implementation, so the same symbol resolves on glibc and muslmacOS — strerror_r (POSIX form)
Windows — strerror_s with reordered args (buffer, size, errnum)
On glibc, plain strerror_r is the GNU form (returns char *) and would
not match this module’s POSIX-shaped IR. The Linux symbol selector
unconditionally picks __xpg_strerror_r and never the GNU form. A
strerror_r fallback remains in the selector as defense-in-depth in case
a future libc drops the __xpg_strerror_r symbol, but the fallback is
unreachable on every libc currently supported (verified by the Alpine
musl_symbol_check CI canary).
numbox.core.bindings._fmtio
Variadic formatted I/O — printf, fprintf, snprintf, sscanf
callable from BOTH plain Python and @njit code.
The public bindings are regular Python functions; numba dispatches to a
private @intrinsic codegen path via @overload when called inside
@njit. Same source runs unchanged in either mode, matching numba’s
own convention for builtins like print and range:
def debug_kernel(x, label):
printf("step %d: %s\n", x, label)
fflush(stdout())
return x * 2
debug_kernel(7, "before") # pure Python: writes via sys.stdout
njit(debug_kernel)(7, "before") # jitted: writes via libc printf
Call convention
User-facing API is C-like — positional *args after the format string;
no tuple wrapper at the call site. Internally the @overload path bundles
the args into a tuple before calling the private _xxx_intrinsic (the
intrinsic itself still uses the tuple-as-args shape because numba’s
@intrinsic typing function doesn’t accept Python-level *args):
printf("x = %d, ratio = %.3f\n", n, ratio)
fprintf(stderr(), "warning: %s\n", msg)
snprintf(array_data_p(buf), buf.size, "[%d:%d]", lo, hi)
sscanf(buf_p, "%d %lf", array_data_p(n_out), array_data_p(x_out))
printf("no args here\n")
Format string must be a literal in @njit
Required so the format string can be embedded as an IR global constant —
the same constraint a C compiler operates under when emitting a
format-checked printf call. A runtime-built unicode raises a clean
TypingError at call typing time. In pure-Python mode the format
string can of course be any str.
Format string encoding: UTF-8
Non-ASCII codepoints in the literal are encoded as UTF-8 byte sequences
and embedded into the IR global. printf treats every non-% byte as
opaque pass-through, so the bytes flow through libc to stdout / FILE* /
the snprintf buffer unmodified. Modern terminals, files, and Windows
10+ consoles all expect UTF-8.
Note
%-Ns width is byte-counted by printf in every libc, so non-ASCII
output won’t right-pad to a codepoint count. That’s printf’s contract.
Pad in numba-side string formatting (f"{s:<10}") before passing
through %s if codepoint-counted widths matter.
Cross-mode caveats
Length modifiers in format strings.
%lld,%ld,%lf,%hd, etc. are valid in C printf but rejected by Python’s%operator. The pure-Python impls strip length modifiers via a regex before formatting (%lld→%d,%.3lf→%.3f). The stripped form produces identical output for typical values because Python ints / floats carry the same width independent of the spec.C-ABI auto-promotion of integer args to 64-bit. The @njit impl widens every integer variadic arg to 64-bit before the libc call (sext / zext as appropriate). Without this, a user writing
printf("%lld", np.int32(7))in @njit would have libc read 8 bytes from a 4-byte source — register garbage in the high bits. With the widening,%lldagainst int32 / int16 / int8 / bool works correctly. Diverges from C ABI (C doesn’t promote int to long long) but matches user expectations and the pure-Python%behavior.String args + ``%s``. Pure-Python’s
%handles strings natively. The @njit @overload auto-convertsunicode_typeargs viaget_unicode_data_pso libc sees a NUL-terminated C string. Users no longer need to callget_unicode_data_pthemselves at the call site.``%ld`` on Win64 (LLP64).
longis 4 bytes on Win64 but 8 on LP64;%ldagainst int64 truncates the high 32 bits on Win64 in @njit. Pure-Python mode hides this because Python’s%ignores length modifiers. Prefer%lld+ int64 for portable 8-byte width.``snprintf`` truncation rc on Windows. Pure-Python and Linux/macOS @njit follow C99 semantics (return would-have-written count). Windows @njit targets MSVCRT
_snprintf(returns-1on truncation, no NUL-term guarantee). Portable check that works on every platform:(rc < 0) or (rc >= size).``fprintf`` to non-stdio FILE* in pure-Python. The Python impl routes
stdout()/stderr()/stdin()handles to the correspondingsys.*streams via an address cache.fopen- returned FILE* values aren’t dereferenceable from Python without a ctypes call, so they raise a clear error in pure-Python mode (useopen()+f.write()for Python-side file I/O).``sscanf`` is @njit-only. Pure-Python implementations of sscanf-style parsing are usually better served by
int()/float()/re; calling from pure-Python raisesNotImplementedError.
References
- numbox.core.bindings._fmtio.fprintf(fp, fmt, *args)[source]
C-style
fprintf(fp, fmt, *args)— dual-mode.fpis a FILE* asintp(fromstdout()/stderr()/stdin()orfopen()).From plain Python: routes
stdout()/stderr()/stdin()handles to the correspondingsys.*streams via an address cache. Arbitrary FILE* values (e.g.fopen-returned) raiseRuntimeError— useopen()+f.write()for Python-side file I/O.From @njit: routes to
_fprintf_intrinsicwith str auto-conversion.
- numbox.core.bindings._fmtio.printf(fmt, *args)[source]
C-style
printf(fmt, *args)— dual-mode (plain Python AND @njit).From plain Python: writes to
sys.stdoutviastr.__mod__after stripping C length modifiers (%lld→%d, etc.).From @njit:
@overloadbelow routes to the private_printf_intrinsicafter auto-converting anyunicode_typeargs viaget_unicode_data_pso libc%sreceives a NUL-terminated C string. Format string must be a literal in @njit (see module docstring for caveats).%nis rejected in both modes (see_reject_percent_n_or_raise).Returns the number of bytes written (or written-equivalent), as int32.
- numbox.core.bindings._fmtio.snprintf(buf_p, size, fmt, *args)[source]
C-style
snprintf(buf_p, size, fmt, *args)— dual-mode.buf_pis anintppointer to the destination buffer (caller- owned). Typicallyarray_data_p(numpy_array)— that helper works in both modes.Returns the number of characters that WOULD have been written if
sizewere unlimited (excluding the trailing NUL), as int32. See the module docstring for the Windows @njit truncation-rc divergence (Python and Linux/macOS @njit follow C99; Windows @njit uses MSVCRT_snprintfwhich returns-1on truncation).
- numbox.core.bindings._fmtio.sscanf(buf, fmt, *args)[source]
C-style
sscanf(buf, fmt, *args)— @njit-only.Args are intp output pointers (typically
array_data_pof a 1-element numpy array of the right dtype). See the @njit-only docstring on_sscanf_intrinsicfor the pointer-vs-spec contract.Pure-Python users: this binding raises
NotImplementedError. For parsing in pure Python useint(),float(), orre.
numbox.core.bindings._math
numbox.core.bindings._sqlite
numbox.core.bindings.call
numbox.core.bindings.signatures
numbox.core.bindings.utils
- numbox.core.bindings.utils.extract_literal_str(binding_name, ty, *, field='argument')[source]
Extract the Python str value of a
Literal[str]type, or raise a cleanTypingErrornaming the binding and the field.Used by intrinsics that require a compile-time string (e.g. printf format strings, libc function names, stdio handle names).
fieldlabels the offending argument in the error message.
- numbox.core.bindings.utils.intp_ll_type(context=None)[source]
LLVM integer type matching numba’s
intpon the current platform.Pass the numba codegen
contextwhen available so the type is derived viacontext.get_value_type(intp)— the canonical pattern for platform-dependent widths (size_t, ssize_t, ptrdiff_t) in intrinsics. When called outside codegen (test helpers, IR-rendering probes), passNonefor the locked-in fallbackllir.IntType(intp.bitwidth); numba’s intp lowering is locked to the same bitwidth, so the two paths produce identical LLVM types.
- numbox.core.bindings.utils.load_lib(name)[source]
Load library libname in global symbol mode. find_library is a relatively basic utility that mostly just prefixes lib and suffixes extension. When adding (custom) libraries to the global symbol scope, consider setting DYLD_LIBRARY_PATH.
- numbox.core.bindings.utils.load_lib_path(path)[source]
Load a shared library by
ctypes.CDLL-acceptable identifier.Accepts any string
CDLLaccepts — an absolute path, a soname (e.g.libm.so.6as returned byctypes.util.find_library), or a bare filename resolvable by the loader. Linux/Darwin useRTLD_GLOBALso symbols reach LLVM’s JIT; Windows useswinmode=0. Unlikeload_lib(name), the handle is returned so callers can check symbol presence withhasattr.