Type dispatch and validation for run-time Python
Project description
Runtype
Runtype is a collection of run-time type utilities for Python.
It is:
:runner: Fast! (benchmarks coming soon)
:brain: Smart! Supports typing
, constraints, auto-casting, and much more.
:gear: Very customizable. You can use dataclass and dispatch with your own typesystem!
Modules
-
:star: dataclass - Adds run-time type validation to the built-in dataclass.
- Improves dataclass ergonomics.
- Supports automatic value casting, Pydantic-style. (Optional, off by default)
- Supports types with constraints. (e.g.
String(max_length=10)
)
-
:star: dispatch - Provides fast multiple-dispatch for functions and methods, via a decorator.
- Inspired by Julia.
-
base_types - Provides a set of classes to implement your own type-system.
- Used by runtype itself, to emulate the Python type-system.
-
isa - Provides a smarter alternative to
isinstance
andissubclass
.
Docs
Read the docs here: https://runtype.readthedocs.io/ (A little outdated. Will fix soon)
Install
$ pip install runtype
No dependencies.
Requires Python 3.6 or up.
Multiple Dispatch
Multiple-dispatch is a state-of-the-art technique for structuring code, that complements object-oriented programming.
Unlike in OOP, where the type of the "object" (or: first argument) is always what determines the dispatch, in multiple-dispatch all the arguments decide together, according the idea of specificity: The more specific classes (i.e. subclasses) get picked before the more abstract ones (i.e. superclasses).
That means that when you need to define a logical operation that applies to several types, you can first solve the most abstract case, and then slowly add special handling for more specific types as required. If you ever found yourself writing several "isinstance" in a row, you could probably use multiple-dispatch to write better code!
Runtype's dispatcher is fast, and will never make an arbitrary choice: in ambiguous situations it will always throw an error.
As a side-effect, it also provides type-validation to functions. Trying to dispatch with types that don't match, will result in a dispatch-error.
Type-Safe Dataclasses
The ability to annotate dataclasses with types has spurred the creation of many great static type-validation tools (such as mypy
). Unfortunately, they can't always predict what types your dataclasses will receive.
The trouble with storing the wrong data, is that it can just sit there for a while, and by time you get the error, it's hard to track which component or thread put it there.
Runtype provides a dataclass
drop-in replacement to Python's native dataclass, that validates the types in runtime, and makes sure you'll see the error the moment something goes wrong, and in the right context.
While Runtype's validation can add a small runtime overhead, it's relatively light. And because it's a drop-in replacement, you can always just switch the import back once you're done debugging.
Examples
Isa & Subclass
from typing import Dict
from runtype import isa, issubclass
isa({'a': 1}, Dict[str, int]) # True
isa({'a': 'b'}, Dict[str, int]) # False
issubclass(Dict[str, int], Mapping[str, int]) # True
issubclass(Dict[str, int], Mapping[int, str]) # False
Multiple Dispatch
from runtype import Dispatch
dp = Dispatch()
@dp
def append(a: list, b):
return a + [b]
@dp
def append(a: tuple, b):
return a + (b,)
@dp
def append(a: str, b: str):
return a + b
print( append([1, 2, 3], 4) ) # prints [1, 2, 3, 4]
print( append((1, 2, 3), 4) ) # prints (1, 2, 3, 4)
print( append('foo', 'bar') ) # prints foobar
print( append('foo', 4) ) # raises DispatchError, no signature for (str, int)
Dataclasses
Basic usage:
>>> from runtype import dataclass
>>> @dataclass
>>> class Point:
... x: int
... y: int
>>> p = Point(2, 3)
>>> p
Point(x=2, y=3)
>>> dict(p) # Maintains order
{'x': 2, 'y': 3}
>>> p.replace(x=30) # New instance
Point(x=30, y=3)
>>> Point(10, 3.5) # Actively validates types
Traceback (most recent call last):
...
TypeError: [Point] Attribute 'y' expected value of type <class 'int'>, instead got 3.5
Using advanced types:
>>> from typing import Optional, Callable
>>> from runtype import dataclass
>>> @dataclass
>>> class Animal:
... name: str
... make_sound: Optional[Callable] = None
>>> Animal("Ant")
Animal(name='Ant', make_sound=None)
>>> Animal("Cat", lambda: print("meow"))
Animal(name='Cat', make_sound=<function <lambda> at ...>)
>>> Animal("Dog", "woof")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
...
[Animal] Attribute 'make_sound' expected value of type typing.Union[typing.Callable, NoneType], instead got 'woof'
Performance
Type verification in classes introduces a slight run-time overhead. When running in production, it's recommended to use the -O
switch for Python. It will skip all assert
s, and also skip type verification on classes by default (use the check_types
option to adjust it manually).
Multiple-dispatch caches call-signatures by default (disable at your own risk!), and should add a minimal overhead after the initial resolution. Dispatch is only 5 to 8 times slower than adding two numbers (see: examples/benchmark_dispatch), which is negligible.
Runtype is not recommended for use in functions that are called often in time-critical code (or classes that are created often).
License
Runtype uses the MIT license.
Donate
If you like Runtype and want to show your appreciation, you can do so at my patreon page, or ko-fi page.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.