Skip to content

Like Types

bearshape has two broad input-contract families:

  • Like types such as F32Like[N, C]
  • ScalarLike types such as U8ScalarLike

Use them when your API accepts values that will be converted, normalized, or range-checked before real work begins.

Basic usage

Like types accept scalars, backend arrays, NumPy arrays, and nested sequences. They must always be subscripted.

import numpy as np
from beartype import beartype
from bearshape import N, C
from bearshape.numpy import F32, F32Like

@beartype
def to_array(x: F32Like[...]) -> np.ndarray:
  return np.asarray(x, dtype=np.float32)

to_array(3.14)  # scalar
to_array([1.0, 2.0, 3.0])  # 1D list
to_array([[1.0, 2.0], [3.0, 4.0]])  # 2D nested list
to_array(np.ones((3, 4)))  # ndarray
to_array([[[[[[1.0]]]]]])  # arbitrarily deep nesting

@beartype
def process(x: F32Like[N, C]) -> F32[N, C]:
  return np.asarray(x, dtype=np.float32)

Use [...] to mean "any rank" or a full dimension spec when the input shape itself matters.

All built-in Like aliases are created with make_array_like_type(..., casting="same_kind"), so dtype compatibility follows NumPy's "same_kind" casting rules by default. For example, int32 can flow into F32Like[...], but complex128 cannot.

Built-in Like aliases

NumPy exports the broadest set:

  • concrete aliases such as BoolLike, I8Like, F32Like, C128Like
  • category aliases such as IntLike, FloatLike, NumLike, ShapedLike

JAX, PyTorch, and CuPy export parallel Like families with backend-specific conversion behavior.

That default matters in practice:

  • F32Like[...] accepts integer inputs that can be cast to float32 under "same_kind"
  • F32Like[...] rejects complex inputs, because complex-to-float is not "same_kind"
  • IntLike[...] accepts integer-like inputs but not floating-point values under the same rule

ScalarLike types (range-validated scalars)

ScalarLike types validate individual scalar values. There is no shape component, only value- and dtype-family constraints.

from beartype import beartype
from bearshape.numpy import U8ScalarLike

@beartype
def clamp_pixel(value: U8ScalarLike) -> int:
  return int(value)

clamp_pixel(128)  # OK
clamp_pixel(256)  # Raises
clamp_pixel(-1)  # Raises

Boolean exclusion

Numeric scalar aliases (I8ScalarLike, F32ScalarLike, NumScalarLike, etc.) reject bool and np.bool_ values. Python bool is a subclass of int, but bearshape treats booleans as non-numeric. Use BoolScalarLike for boolean scalars.

Available families include:

  • concrete aliases: BoolScalarLike, I8ScalarLike through I64ScalarLike, U8ScalarLike through U64ScalarLike, F16ScalarLike through F128ScalarLike, C64ScalarLike, C128ScalarLike, C256ScalarLike
  • category aliases: IntScalarLike, FloatScalarLike, RealScalarLike, NumScalarLike, ShapedScalarLike, and others
  • StringLike for str | np.str_

Backend modules re-export these NumPy-defined scalar types:

from bearshape.numpy import U8ScalarLike
from bearshape.jax import U8ScalarLike
from bearshape.torch import U8ScalarLike
from bearshape.cupy import U8ScalarLike

Note

Backend-native 0-D arrays such as jnp.array(1.0) or torch.tensor(1.0) are not ScalarLike. Use a Like alias with Scalar, for example F32Like[Scalar].

Backend-specific conversion behavior

The Like family is intentionally backend-aware:

  • bearshape.numpy slow-path conversion uses np.asarray
  • bearshape.jax slow-path conversion uses jnp.asarray, so objects implementing __jax_array__ are accepted
  • bearshape.torch slow-path conversion uses torch.as_tensor
  • bearshape.cupy slow-path conversion uses cupy.asarray

Only native backend arrays use the default metadata fast path. Foreign arrays, including NumPy arrays passed to JAX, Torch, or CuPy, must pass the target converter. A failed backend conversion is never rescued by a NumPy fallback. For example, Torch Like rejects negative-stride and non-native-endian NumPy arrays because torch.as_tensor rejects those layouts.

Validation checks convertibility at that moment and may allocate an array. It does not replace the original function argument. Same-kind casting does not promise lossless conversion. Custom factory authors supplying trusted_types assert that those types can bypass their converter; use an empty tuple to force conversion for every input.

Static NumPy/JAX/Torch Like aliases include supported scalar and nested-sequence input families. Convert explicitly inside the function to get a native backend result. See Static Typing for tested calls and limitations.

Custom ScalarLike types

Use make_scalar_like_type when the built-in scalar families are close but not quite right for your API:

import numpy as np
from bearshape.numpy import make_scalar_like_type

F32ScalarDefault = make_scalar_like_type(np.float32)  # same_kind
F32ScalarStrict = make_scalar_like_type(np.float32, casting="no")
F32ScalarSafe = make_scalar_like_type(np.float32, casting="safe")
F32ScalarUnsafe = make_scalar_like_type(np.float32, casting="unsafe")

Useful interpretations:

  • casting="no" means "exact target dtype only"
  • casting="safe" means "no information loss"
  • casting="same_kind" means "same numeric family" and is the default
  • casting="unsafe" is the most permissive option and should be used deliberately

target_dtype may be passed as a NumPy scalar type, a dtype object, or a canonical dtype string such as "float32".

Numeric ScalarLike factories still reject booleans by design:

U8Scalar = make_scalar_like_type(np.uint8)
BoolScalar = make_scalar_like_type(np.bool_)

# U8Scalar rejects True / False
# BoolScalar accepts True / False

make_scalar_like_type is intentionally documented on backend modules such as bearshape.numpy; it is not part of the lightweight root bearshape import surface.

Casting rules

Both make_array_like_type and make_scalar_like_type use NumPy casting semantics:

Casting Meaning Example for target float32
"no" Exact dtype only only float32
"equiv" Same kind and size float32 but not float64
"safe" No information loss int16 yes, float64 no
"same_kind" Same-kind conversion int32 yes, complex64 no
"unsafe" Any cast NumPy allows very permissive

Default used by built-in Like aliases

The built-in aliases such as F32Like, I64Like, IntLike, FloatLike, and NumLike all use:

casting="same_kind"

That is the library default for make_array_like_type(...), and it is the behavior you get unless you build a custom alias yourself.

If you need stricter or looser input acceptance, make a custom alias instead of relying on the built-ins:

from bearshape import make_array_like_type
from bearshape._dtypes import FLOAT32

F32Exact = make_array_like_type(FLOAT32, casting="no", name="F32Exact")
F32Unsafe = make_array_like_type(FLOAT32, casting="unsafe", name="F32Unsafe")

ArrayLike template

bearshape.numpy.ArrayLike is a public recursive type alias for custom static typing combinations:

from typing import TypeAlias

import numpy as np
from bearshape.numpy import ArrayLike

MyInputType: TypeAlias = ArrayLike[float, np.float32]

That template is most useful when you want your own checker-friendly alias but still follow bearshape's "scalar or nested sequence or array" model.

Static input types

Like annotations describe the input value. They do not replace it with an array; convert explicitly before using backend-specific methods. NumPy numeric Like aliases admit the input dtype families allowed by same_kind casting: booleans for BoolLike; booleans and unsigned integers for unsigned Like types; booleans and integers for signed integer Like types; those plus floating values for real Like types; and all numeric kinds for complex Like types. Input precision may differ from the target precision. Casting can lose precision.

JAX and Torch Like annotations accept native arrays, numeric NumPy arrays/scalars, and ordinary numeric nested sequences. Their native array types do not encode dtype statically, so runtime checks still decide casting, shape, device, byte order and actual converter support. A NumPy __array__ method alone is not a static guarantee of Torch conversion.

NumPy Shaped accepts every NumPy generic dtype, including string, object, datetime and structured arrays. ShapedLike also admits ordinary string/bytes sequences. Shape relationships remain runtime checks in every checker.