Welcome to Threecheck’s documentation!

Threecheck is a simple typechecking system written for Python 3000, which is hosted at http://threecheck.sourceforge.net (you can download it at http://www.sourceforge.net/projects/threecheck) . Its features are:

  • base type checking (int, float, str, ...)
  • list, set, dict and tuple nested type checking (e.g.: list of int)
  • contract checkers: callables, iterables, generic attribute checker
  • boolean operators: and, or, not, xor
  • support for *varargs and **kwargs
  • fixed value check
  • clear interface for extending with new checkers: criteria checker and more powerful Checker class subclassing

Here’s a simple example of its capabilities, which shows how to annotate functions:

>>> import threecheck as tc
>>> @tc.typecheck
... def pow(base: tc.Number, exp: int) -> tc.Number:
...   res = 1
...   for i in range(exp): res=res*base
...   return res
>>> @tc.typecheck
... def reverseDict(d: {str: int}) -> {int: str}:
...   return {v: k for k, v in d.items()}
>>> @tc.typecheck
... def mult(const: tc.Or(str, int), lst: tc.Iterable(int)) -> tc.Iterable(int):
...   return [v * int(const) for v in lst]

Tutorial

Here’s a walkthrough to all Threecheck capabilities, skipping the technicalities behind the system.

Base typechecking

The minimum framework to use for typechecking a function or method is the typecheck() decorator and some annotations on parameters and return values:

>>> import threecheck as tc
>>> @tc.typecheck
... def sum(a: int, b: int) -> int:
...   return a+b

The typecheck() decorator enables the typechecking system, and the annotation describe the types of the parameters and return value. The most basic type of annotation you can put, as we have seen, is the type to match. Any type or class is valid. Subclasses are matched too:

>>> class Base: pass
...
>>> class Derived(Base): pass
...
>>> @tc.typecheck
... def f(x: Base):
...   pass
...
>>> f(Derived())

The function executes without any error, because a Derived instance is also a Base instance. In case of typechecking error, a TypeCheckFailed exception is raised.

Compound types

Complex data types are easily matched too. If we want to simply match a list of any object, we already have the instruments to do it:

>>> @tc.typecheck
... def f(lst: list):
...   pass

But we can also express a list of ints with this syntax:

>>> @tc.typecheck
... def f(lst: [int]):
...   pass

The following examples match sets or dictionaries:

>>> @tc.typecheck
... def f(s: {float}):
...   pass
... @tc.typecheck
... def g(d: {int: str}):
...   pass

The function f accepts a set of floats, whereas the function g accepts a dictionary which has int as keys and str as values.

Tuples are considered too. Let’s say we want to match tuples whose first element is an int, the second one a str, and the third one a float:

>>> @tc.typecheck
... def f(x: (int, str, float)):
...   pass

This way f((3, "hello", 4.2)) is accepted, whereas f((3, "hello", "hello2")) raise a TypeCheckFailed exception. If you want instead a tuple made of many elements of the same type, you have to use the VarTuple class:

>>> @tc.typecheck
... def f(x: tc.VarTuple(int)):
...   pass

This way f((1, 2)), f((1, 4, 3, 5)) and f((8, 5, 6)) are accepted, whereas f((1, 2, "hello")) is not.

More base typechecking

Typecheckers can be combined to express more powerful concepts. One way of combining them is through the boolean operators: And, Or, Not and Xor:

>>> @tc.typecheck
... def sum(values: [tc.Or(int, str)]) -> int:
...     return sum(map(int, values))

In this example we can see that builtin types and complex types are interchangeable.

Using bitwise python operators (& for And, | for Or, ~ for Not and ^ for Xor), we can express the boolean operators more clearly. The problem is that builtin python types don’t support such operands, and thus must be wrapped using the Type operator, as in the following example, which is perfectly equivalent to the previous one:

>>> @tc.typecheck
... def sum(values: list(tc.Type(int) | tc.Type(str))) -> int:
...     return sum(map(int, values))

This time, the expression got longer, but clearer. It’s up to your taste decide which expression suits you better. Any other threecheck operator already supports the bitwise operators overloading.

Threecheck offers also a set of types to express typical situations. Ranges (minimum and maximum are included):

>>> @tc.typecheck
... def processDiceRoll(value: tc.Range(1, 6)):
...     # ...

Constant values:

>>> @tc.typecheck
... def printNot10(value: tc.Type(int) & ~tc.Value(10)):
...   print(value)

Nullables and not nullables:

>>> @tc.typecheck
... def getCurrentUser() -> tc.Nullable(User):
...     # ...
>>> @tc.typecheck
... def setContent(content: tc.NotNone(object)):
...     # ...

And some more contract-based checkers, like iterables:

>>> @tc.typecheck
... def skipLast(i: tc.Iterable) -> tc.Iterable:
...   return list(i)[:-1]

And callables:

>>> @tc.typecheck
... def apply(func: tc.Callable, args: list, kwargs: {str: object}):
...     return func(*args, **kwargs)  #*

Advanced typechecking

Type checking of classes poses some more problems, due to the fact that during the definition of a class you can’t refer to the class itself:

>>> class Merger:
...   @tc.typecheck
...   def merge(self, other: Merger) -> Merger:
...     # doesn't work: 'Merger' not known at this time
Traceback (most recent call last):
    ...
NameError: name 'Merger' is not defined

And that mutual references are impossibile to express right away:

>>> class Parent:
...   @tc.typecheck
...   def getChild(self) -> Child:
...     # doesn't work: 'Child' not known at this time
...
Traceback (most recent call last):
    ...
NameError: name 'Child' is not defined
>>> class Child:
...   @tc.typecheck
...   def getParent(self) -> Parent:
...     # this works fine, since 'Parent' has already been defined

To solve the first problem, you can use the special Self:

>>> class Merger:
...   @tc.typecheck
...   def merge(self, other: tc.Self) -> tc.Self:
...     # it's ok

Whereas in the second case, you can use the Type class in the “lazy” format, which use the name of the type – instead of the type itself – so to resolve it at a later time:

>>> Child = tc.Type("Child")
>>> class Parent:
...   @tc.typecheck
...   def getChild(self) -> Child:
...     # now works fine
...
>>> class Child:
...   @tc.typecheck
...   def getParent(self) -> Parent:
...     # this works fine, since 'Parent' has already been defined

You can also use this Type construct inline if you prefer:

>>> class Parent:
...   @tc.typecheck
...   def getChild(self) -> tc.Type("Child"):
...     # now works fine
...
>>> class Child:
...   @tc.typecheck
...   def getParent(self) -> Parent:
...     # this works fine, since 'Parent' has already been defined

This lazy use of the Type construct can also solve the first problem, you decide the form you prefer.

Threecheck supports also a basic type of generators, the one which always satisfy a single constraint. This is expressed just like any other checker:

>>> @tc.typecheck
... def getSquares(maximum) -> int:
...   for x in range(maximum):
...     v = x*x
...     if v < maximum:
...       yield v
...     else:
...       break

Threecheck system recognize it’s been applied to a generator, and thus apply the checker to every returned value. Future versions will implement also more complex patterns of return values.

Full builtin checker list

The checker system is completely based on the Checker subclasses, whose instances perform the true checking. Some values which are not subclasses of Checker are also admitted, like the list form ([int]), and they generate a corresponding Checker instance.

class And(*checkers)
Matches a value only if the two sub-checkers are matched. A shorthand for this checker is the & operator.
class Any

Matches any value. Not putting any annotation is the same.

>>> @tc.typecheck
... def doubleId(o: tc.Any) -> int:
...   return id(o)*2
class Attrs(*attributes)

Check that the value has certain attributes defined. This is a way to perform duck-typing, in contrast with the classical typing given by the Type checker.

>>> @tc.typecheck
... def quack(duck: tc.Attrs('walk', 'quack')):
...   duck.walk()
...   duck.quack()
Callable
Check the value is a callable.
class Criteria(criteria)

An user-definable criteria to check the values with: this is the simplest way to extend the threecheck system. The checker function must return a truth value. A shorthand for this class is the callable itself.

>>> def even(x): return x % 2 == 0
>>> @tc.typecheck
... def doubleEven(x: even) -> tc.Not(even):
...   return x+1
class Dict(keys, values)
Check the value is a dictionary. The dictionary “{k: v}” operator is a shorthand for this checker.
class Iterable(subtype = None)
Check whether a value is an iterable. Can take a subchecker applied to each value. Notice that the iterable will be consumed, if it’s not a sequence, during the checker, in this subcase!
List(subtype = None)
Check a value is a list. Can take a subchecker which is applied to each value. The list “[...]” operator is a shorthand for this checker.
class Not(checker)
Invert the logic of the inner matcher.
NotNone(checker = Any)
Check a value is not None.
Nullable(checker = Any)
Check the value is None, or match the inner checker.
Number
Check a value is of a number type, that is, either int, float or complex.
Positive
Check the value is a number which is zero or greater (complex values are not valid for this checker).
class Or(*checkers)
Matches a value if at least one of the two sub-checkers is matched. A shorthand for this checker is the | operator.
class Range(min = None, max = None)

Matches a certain range of values, extremes included. You can skip an extreme to say you ignore that limit.

>>> @tc.typecheck
... def absTan(x: tc.Range(0, math.pi/2)) -> tc.Range(min=0):
...   return math.abs(math.tan(x))
Set(subtype = None)
Matches a set. The set operator “{...}” is a shorthand for this operator.
class Tuple(*subtypes)
Matches a tuple of n components.
class Type(t)

Matches a specific type. It can either be used in the non-lazy form, giving the type to check as argument, or the lazy form, giving the name of the type to check as argument. In this case, the first time the typechecking is required, the type is resolved looking between the global and local symbols known at the scope of definition.

A shorthand for the Type checker is the type itself.

class Value(value)

Check that a type has a specific value. Usually it’s useful in combinations with the boolean operators.

>>> @tc.typecheck
... def invertBinary(x: tc.Value(0) | tc.Value(1)) -> tc.Value(0) | tc.Value(1):
...   return 1-x
class VarTuple(subtype = None)
Matches a tuple made of a single type repeated many times.
Xor(*checkers)
Matches a value if just one of the two sub-checkers is matched. A shorthand for this checker is the ^ operator.

Extending checker system

There are two ways to extend the Threecheck typing system: using the Criteria class, which is simple, or using the Checker class, which is more powerful. But before making your own checker from scratch, you can think about building it from the base operators by combining them.

Combining existing operators

A couple of examples can be taken from the library itself. In example, the Positive checker is expressed as follows:

>>> Positive = Or(int, float) & Range(min=0)

Which tells you that a positive number is either an int or a float with a minimum value or zero. A more complex example is the Xor() operator, which needs arguments too, and thus is a function:

>>> def Xor(\*checkers):
...     return Or(\*[
...         And(\*([checker] + [
...             Not(checker2)
...             for checker2
...             in checkers
...             if checker is not checker2]))
...         for checker
...         in checkers])

Anyway, if combining existing operators isn’t enough for you, you can use the following classes.

Criteria class

The Criteria checker accepts any function which act as a checker. Every callable is automatically wrapped inside a Criteria checker, and thus can be directly used:

>>> def even(x): return x % 2 == 0
>>> @tc.typecheck
... def doubleEven(x: even) -> tc.Not(even):
...   return x+1

Checker class

class Checker

The most powerful system to extend the checker system is to build upon the Checker class. The added value is that you have more control over the error messages and informations not only about the argument checked itself, but also about the other arguments which can sometimes be useful. The Checker class methods to implements follows.

error(value, args, kwargs)

Check if a value is ok for this checker. value is the value to check, whether args is a tuple containing all the non-keyword arguments passed to the function, and kwargs is a dict between keyword arguments and values. If the check is passed, must return an empty string, otherwise a string containing a message error.

ok_message()

Return a message which explains why this checker has succeeded (in case it succeeds). E.g.: we have a checker for type int, and its ok_message implementation can be:

>>> def ok_message(self):
...   return 'is an int'

Indices and tables