https://github.com/ceronman/typeannotations
The typeannotations module provides a set of tools for type checking and
type inference of Python code. It also a provides a set of types useful for
annotating functions and objects.
These tools are mainly designed to be used by static analyzers such as linters, code completion libraries and IDEs. Additionally, decorators for making run-time checks are provided. Run-time type checking is not always a good idea in Python, but in some cases it can be very useful.
The typechecked decorator can be used to check types specified in function
annotations. For example:
>>> @typechecked
... def test(a: int) -> int:
... return a
...
>>> test(1)
1
>>> test('string')
Traceback (most recent call last):
...
TypeError: Incorrect type for "a"The Interface class allows you to define interfaces that are checked
dynamically. You don't have to explicitly indicate when an object or class
implements a given Interface. If an object provides the methods and
attributes specified in the Interface, it's considered a valid
implementation.
For example, let's define a simple interface:
>>> class Person(Interface):
... name = str
... age = int
... def say_hello(name: str) -> str:
... passAny object defining those the name, age and say_hello() members is
a valid implementation of that interface. For example:
>>> class Developer:
... def __init__(self, name, age):
... self.name = name
... self.age = age
... def say_hello(self, name: str) -> str:
... return 'hello ' + name
...
>>> isinstance(Developer('bill', 20), Person)
TrueThis also works with built-in types:
>>> class IterableWithLen(Interface):
... def __iter__():
... pass
... def __len__():
... pass
...
>>> isinstance([], IterableWithLen)
True
>>> isinstance({}, IterableWithLen)
True
>>> isinstance(1, IterableWithLen)
FalseA typedef is similar to an Interface except that it defines a single
function signature. This is useful for defining callbacks. For example:
>>> @typedef
... def callback(event: Event) -> bool:
... pass
...Then it's possible to check if a function implements the same signature:
>>> def handler(event: MouseEvent) -> bool:
... print('click')
... return True
...
>>> isinstance(handler, callback)
True
>>> isinstance(lambda: True, callback)
FalseNote that MouseEvent is a subclass of Event.
A union is a collection of types and it's a type itself. An object is an
instance of a union if it's an instance of any of the elements in the union.
For example:
>>> NumberOrString = union(int, str)
>>> isinstance(1, NumberOrString)
True
>>> isinstance('string', NumberOrString)
True
>>> issubclass(int, NumberOrString)
True
>>> issubclass(str, NumberOrString)
TrueA predicate is a special type defined by a function that takes an object
and returns True or False indicating if the object implements the type.
For example:
>>> Positive = predicate(lambda x: x > 0)
>>> isinstance(1, Positive)
True
>>> isinstance(0, Positive)
FalsePredicates can also be defined using a decorator:
>>> @predicate
... def Even(object):
... return object % 2 == 0Predicates can also be combined using the &` operator:
>>> EvenAndPositive = Even & PositivePredicates are useful for defining contracts:
>>> Positive = predicate(lambda x: x > 0)
>>> @typechecked
... def sqrt(n: Positive):
... ...
>>> sqrt(-1)
Traceback (most recent call last):
...
TypeError: Incorrect type for "n"The optional predicate indicates that the object must be from the given type
or None. For example:
>>> isinstance(1, optional(int))
True
>>> isinstance(None, optional(int))
TrueAnd checking types at runtime:
>>> @typechecked
... def greet(name: optional(str) = None):
... if name is None:
... print('hello stranger')
... else:
... print('hello {0}'.format(name))
...
>>> greet()
hello stranger
>>> greet('bill')
hello billThe only predicate indicates that an object can only be of the specified
type, and not of any of its super classes. For example:
>>> isinstance(True, only(bool))
True
>>> isinstance(1, only(bool))
FalseNote that in Python bool is a sublcass of int.
The options predicate indicates that the value of an object must be one of
the given options. For example:
>>> FileMode = options('r', 'w', 'a', 'r+', 'w+', 'a+')
>>> isinstance('w', FileMode)
True
>>> isinstance('x', FileMode)
FalseThis is useful when defining a function:
>>> @typecheck
... def open(filename: str, mode: options('w', 'a')):
... ...Complex types are also accepted in both interfaces and type specifications.
>>> @typechecked
... def test(a: { int: ( str, bool ) }) -> (bool, int):
... return isinstance(a, dict), len(a)
...
>>> test({ 1: ('a', False) })
(True, 1)
>>> test('string')
Traceback (most recent call last):
...
TypeError: Incorrect type for "a"The rules are:
- A list of types. The value must be a list containing only the specified types.
- A set of types. The value must be a set containing only the specified types.
- A tuple of types. The value must be a tuple containing the specified types in the specified order.
- A dict of types. The value must be a dict where each (key, value) pair is assocated with a (key, value) pair in the type dictionary.
Any of the complex types can nest and contain any other type.
@overload
def isinstance(object, t: type):
...
@overload
def isinstance(object, t: tuple):
...@annotate('builtins.open')
def open_annotated(file: str,
mode: options('r', 'w', 'a', 'r+', 'w+', 'a+'),
buffering: optional(int)) -> IOBase:
pass