Skip to content

Latest commit

 

History

History
127 lines (89 loc) · 4.01 KB

File metadata and controls

127 lines (89 loc) · 4.01 KB

isinstance2

isinstance2 is an experimental module that provides a powerful runtime type checker for Python's built-in generic classes and generic type hints. It allows you to perform runtime instance type checks on objects that are instances of a generic class, as well as subclass checks on generic classes, even if you don't know the exact type of the generic parameters.

tl;dr

from isinstance2 import isinstance2, issubclass2
from typing import Iterable

assert isinstance2((1, 2.0, 'three'), tuple[int, float, str])
assert issubclass2(dict[str, int], dict[str, int | float])
assert issubclass2(list[int] | set[int] | tuple[int, ...], Iterable[int])

Features

  • Perform runtime instance and subclass checks on generic classes
  • Supports built-in generic classes such as list, tuple, dict, set, and frozenset, as well as Optional and Literal.
  • Check if an object is an instance of a tuple with variadic arguments.
  • Register custom class or function with isinstance2's instance checker registry.

Installation

pip install isinstance2

Basic Usage

Instance Checks

from typing import Iterable, Literal
from isinstance2 import isinstance2

# Basic instance checks
assert isinstance2([1, 2, 3], list[int])
assert isinstance2((1, 2.0, 'three'), tuple[int, float, str])
assert isinstance2({1, 2, 3}, set[int])
assert isinstance2({"foo": 1, "bar": 2}, dict[str, int])
assert isinstance2(frozenset([1, 2, 'Hi! 😊', 'literally amazing']), frozenset[int | Literal['Hi! 😊', 'literally amazing']])

# Ellipses in tuples work
assert isinstance2((1, 'two', 3.0, 'four'), tuple[int | float | str, ...])

# You can also check against abstract generic classes
assert isinstance2(range(10), Iterable[int])
assert not isinstance2(range(10), Iterable[float])

Subclass Checks

from typing import Collection, Iterable
from isinstance2 import issubclass2

# Basic subclass checks
assert issubclass2(list[int], list[int | float])
assert issubclass2(tuple[int, float], tuple[int | float, ...])

# Classes without generic parameters are presumed to match
assert issubclass2(list, list[int])
assert issubclass2(list[int], list)

# Abstract generic classes
assert issubclass2(list[int], Iterable[int])
assert issubclass2(Collection[bool], Iterable[int])  # Yes, bool is a subclass of int

Advanced Usage

To check if an object is an instance of a custom generic class, register it with isinstance2's instance checker

from typing import Generic, TypeVar, Any
from isinstance2 import isinstance2, register_instance_checker

T = TypeVar('T')


class MyClass(Generic[T]):
    ...


@register_instance_checker
def is_instance_of_my_class(obj: Any) -> bool:
    return isinstance(obj, MyClass)


assert isinstance2(MyClass(), MyClass)

If you'd prefer not to add your checkers globally, you can use isinstance2's register instead and pass a custom registry (which is just a dict).

from typing import Generic, TypeVar
from isinstance2 import register, instance_checker_registry
from functools import partial

# Copy the default registry
my_registry = instance_checker_registry.copy()

# Make a custom registration function
my_register = partial(register, registry=my_registry)

Now you can use my_register in place of register_instance_checker.

Limitations

  • Does not yet support
    • TypeVar
    • Container
    • And likely quite a few other generic classes that I've missed. Please open an issue if you find one.
    • Subclass checks for custom classes (instance checks are supported)
  • Subclass checks are, in general, unreliable.
    • I haven't yet figured out how to deal with things like structural subtyping. For instance, issubclass2(str, Iterator[int]) currently returns True when it clearly shouldn't
    • Instance checks are somewhat simpler and shouldn't suffer as much from this problem.
  • Requires Python 3.11 or later

License

isinstance2 is released under the MIT License.