Skip to content

Types

Types

This package provides a bunch of useful types. They all support isinstance, issubclass and calling as validation.

Behavior

For type checking, each class defaults to the listed base class (not DynamicInstantiation). This means if you have a function like below, any number can be used for p.

from extra_types.types import Prob # float when static type checking

def bernoulli(p: Prob):
    # p not type checked at run time

bernoulli(0.5) # this is fine
bernoulli(2.0) # this passes static type checking
bernoulli("NaN") # this will give you an error with a static type checker

There a few ways to improve this function:

from extra_types.types import Prob
from extra_types.type_utils import strict_cast

def bernoulli(p: Prob):
    assert isinstance(p, Prob) # AssertionError
    p = Prob(p) # TypeError
    p = strict_cast(Prob, p) # TypeError

All of these will fail if you use 2.0 as your probability.

You can also use other libraries, like typeguard and attrs for automatic validation:

import attrs

@attrs.define
class AgentConfig:
    agent_size: PosInt = attrs.field(validator=attrs.validators.instance_of(PosInt))


import typeguard

@typeguard.typechecked
def decrement(x: PosInt) -> Nat:
    return x - 1

Despite what it looks, you cannot create an instance of these classes (unless you do some magic of your own). Take this sample:

p = Prob("0.5") # Checks that p is valid.
assert type(p) is float # Always True

This makes it compatible with any other code.

The same logic applies to the other types!

extra_types.types

Char

Bases: DynamicInstantiation, str

A character type representing strings of length one.

Source code in extra_types/types/char.py
class Char(DynamicInstantiation, str, metaclass=DynamicCheck):
    """A character type representing strings of length one."""

    @classmethod
    def _is_instance(cls, instance: object) -> bool:
        return issubclass(type(instance), cls) and len(cast(str, instance)) == 1

    @classmethod
    def _is_subclass(cls, sub_cls: type) -> bool:
        return sub_cls is str

Nat

Bases: DynamicInstantiation, int

A natural number type representing integers greater than or equal to zero.

Source code in extra_types/types/nat.py
class Nat(DynamicInstantiation, int, metaclass=DynamicCheck):
    """A natural number type representing integers greater than or equal to zero."""

    @classmethod
    def _is_instance(cls, instance: object) -> bool:
        return issubclass(type(instance), cls) and instance >= 0

    @classmethod
    def _is_subclass(cls, sub_cls: type) -> bool:
        return sub_cls is int

PosInt

Bases: DynamicInstantiation, int

A positive number type representing integers strictly greater than zero.

Source code in extra_types/types/pos_int.py
class PosInt(DynamicInstantiation, int, metaclass=DynamicCheck):
    """A positive number type representing integers strictly greater than zero."""

    @classmethod
    def _is_instance(cls, instance: object) -> bool:
        return issubclass(type(instance), cls) and instance > 0

    @classmethod
    def _is_subclass(cls, sub_cls: type) -> bool:
        return sub_cls is int

Prob

Bases: DynamicInstantiation, float

A numeric type representing values between 0 and 1 inclusive.

Source code in extra_types/types/prob.py
class Prob(DynamicInstantiation, float, metaclass=DynamicCheck):
    """A numeric type representing values between 0 and 1 inclusive."""

    @classmethod
    def _is_instance(cls, instance: object) -> bool:
        return issubclass(type(instance), cls) and 0 <= cast(float, instance) <= 1

    @classmethod
    def _is_subclass(cls, sub_cls: type) -> TypeIs[type[float]]:
        return issubclass(sub_cls, float)