Language reference

This section describes PyContracts’ language for specifying contracts.

Simple types

The simplest kind of contract consists in declaring the type of a value. These are the supported primitive types:

dict, list, tuple, float, int, number, array, bool, None

Moreover, there are two kinds of wild-cards:

*
Matches any object.
#
Never matches anything — useful for debugging.

Variables

PyContracts allows to use variables to bind to sub-expressions and reuse later (it is simpler than it sounds).

There are two kinds of variables. The first kind is denoted by an uppercase letter, and it is constrained to bind to integer values.

A B C D E F G H I J K L M N O P Q R S T U W V X Y Z

Lower-case letters denote general-purpose variables that can bind to any type:

a b c d e f g h i j k l m n o p q r s t u w v x y z

Note

The reason for having specialized variables for integers is to encourage a writing style in which uppercase letters denote lengths, shapes, etc. Moreover, an error will be thrown if they do not bind to integers, which helps in catching mistakes.

Scoped variables

Contracts can refer to external variables using $PythonVariable. For example, a typical application is the following:

from module import MyClass

@contract(x='list($MyClass)')
def f(x):
  pass

Logical expressions

Two logical operators are supported:

AND

Expressed with a comma (,).

For example, the expression

check("contract1,contract2", value)

is roughly equivalent to

check("contract1", value) and check("contract2", value)

except that variables in the contracts are evaluated in the same context.

OR

Expressed with a pipe symbol (|).

The semantic is that the first matching expression is chosen.

The expression:

check("contract1|contract2", value)

is roughly equivalent to:

if value respects "contract1":
   return True
elif value respects "contract2":
   return True
else:
   return False

The AND has precedence over OR. For example, the expression

a,b|c

is evaluated the same as

(a, b) | c
Examples of contracts with logical operators
Contract expression Meaning
None|int Either None or an integer.

Equality

This section must still be written.

Arithmetic and comparison

This section must still be written.

Details of variable binding

This section must still be written.

Lists

You can specify that the value must be a list, and specify optional constraints for its length and for its elements.

The general syntax is:

list
list[length_contract]
list(elements_contract)
list[length_contract](elements_contract)
Examples of list contracts
Contract expression Meaning
list An instance of list.
list[2] A list of two elements.
list(int) A list of integers.
list(number) A list of numbers.
list[3](number) A list of exactly three numbers.
list[>=3](number) A list of at least three numbers.
list[>=3](number, >0) A list of at least three numbers, greater than 0.
tuple(list[N], list[N]) A tuple of two lists of the same length.

Sequences

The contract seq has the same syntax as list but matches any sequence.

seq
seq[length_contract]
seq(elements_contract)
seq[length_contract](elements_contract)

Tuples

You can either specify a length (with square brackets), or specify a contract for each element:

tuple

tuple[length]

tuple(element1, ..., elementN)

In the latter case, you are also specifying implicitly the number of elements.

Note

The syntax for tuples is somewhat a special case. While list(int,>0) signifies a list of positive integers (or empty list), tuple(int, >0) means a tuple with exactly 2 elements, the first of which should be an integer, and the second must be positive (but not necessarily integer.)

Examples of tuple contracts
Contract expression Meaning
tuple A tuple.
tuple[2] A tuple with two elements.
tuple(*,*) A tuple with two elements.
tuple(int) A tuple with one integer element.
tuple(int, int) A tuple with two integer elements.
tuple[>=2] A tuple with at least two elements.

Dictionaries

For dictionary, you can specify a length contract, as well as a contract for its keys and values:

dict

dict[length_contract]

dict(key_contract: value_contract)

dict[length_contract](key_contract: value_contract)
Examples of dict contracts
Contract expression Meaning
dict Any dictionary.
dict[2] A dictionary with two elements.
dict(*: *) Any dictionary.
dict(*: int) A dictionary whose values are integers.
dict(str: *) A dictionary whose keys are strings
dict(str: tuple(type(x),type(y)) ), x!=y A dictionary whose keys are strings, and whose values are tuples with two elements, of two different types x and y.

Mappings

The contract map has the same syntax as dict but matches any mapping.

map

map[length_contract]

map(key_contract: value_contract)

map[length_contract](key_contract: value_contract)

Numpy arrays

The support for Numpy arrays was one of the motivations for me to develop PyContracts. Numpy is one of the best and most useful Python packages around. It supports the ndarray datatype which allows for operations with tensors of arbitrary dimensions.

All of that is very powerful, but it might be a bit confusing, especially because Numpy tends to be very liberal when it gets to operations between arrays. For example, the operation A * B, if A is a 2D matrix, is well defined when B is a scalar, a vector, or a matrix. Sometimes you want to be certain of your assumptions, otherwise you risk of ignoring powerful bugs.

Note

Researchers tend to be a bit paranoid, because often you don’t know what to expect out of your algorithms. A stupid bug can lead to an exciting (false) discovery!

So PyContracts offers several shortcuts for Numpy arrays, and the error messages tend to be more descriptive.

The general syntax looks like this:

array

array[shape_contract]

array(numpy_contract)

array[shape_contract](numpy_contract)

Shape contracts

A shape contract is specified using a special syntax of the kind:

dimension1 x dimension2 x dimension3

dimension1 x dimension2 x dimension3 x …

The ellipsis is part of the syntax and specifies that more dimensions are allowed.

Each dimension can be specified as a number, with variables, or as a contract.

Examples of array shape contracts
Contract expression Meaning
array Any array.
array[3] A 1D array with 3 elements (shape=(3,)).
array[3x2] A 2D array with 3 rows and 2 columns (shape=(3,2)).
array[3 x ...] A nD array with 3 rows and arbitrary other dimensions.
array[3xN], N>=2 A 2D array with 3 rows and at least 2 columns.
array[NxN], N>0 A square matrix.
array[NxNx...], N>0 A tensor with the first two dimensions of equal size.
list(array[NxN]), N>0 A list of square matrices of the same size.

Numpy-specific contracts

A numpy contract is special and implemented separately from the other PyContracts contract. Right now, we support:

Datatype contracts

These specify the datatype of the array. Available:

uint8 uint16 uint32 uint64 int8 int16 int32  int64 float32 float64
Arithmetic comparisons

These have the semantics that all elements must satisfy the contract. They appear similar to the normal PyContracts comparison:

>0   <0   <=1   =0
Examples of array elements contracts
Contract expression Meaning
array Any array.
array(>=0) An array with nonnegative elements.
array(int8,>=0) An array with nonnegative integer elements.
array[NxN](float,>=0,<=1) A square matrix with float elements in the interval [0,1].
array[NxN](int,(0|1)) A square matrix with each element equal either to 0 or 1 (this could model a directed graph).

Other examples

Here is an example that first uses new_contract() to specify a domain-specific array type (rgb, rgba), then uses PyContracts to be sure that the two given images and the result values have coherent dimensions. Notice how the contracts serve very well as documentation.

from contracts import contract, new_contract

new_contract('rgb',  'array[HxWx3](uint8),H>0,W>0')
new_contract('rgba', 'array[HxWx4](uint8),H>0,W>0')

@contract
def blend_images(image1, image2):
    '''
         Blends two images together.

         :param image1: The first image to blend.
         :type image1: rgb,array[HxWx*]

         :param image2: The second image to blend. Can have an alpha layer.
         :type image2: (rgb|rgba),array[HxWx*]

         :return: The blended image.
         :rtype: rgb,array[HxWx3]
    '''
    ...

If you want to have a function that accepts a list of images, you would write:

@contract
def blend_images(images):
    '''
         Blends a series of images together.

         :type images: list[N](rgb,array[HxWx*]), N>=2

         :rtype: rgb,array[HxWx3]
    '''
    ...

Advanced topic: the context isolate operator

PyContracts has a special construct $(...) used to separate the context of variables. The semantics is that the variables assigned inside $(...) are not propagated outside.

Examples of $() contracts
Contract expression Meaning
list(tuple(type(x),type(y)),x!=y) A list whose elements are tuples with two elements, of two different types x and y (x and y are common among the list elements ).
list( $( tuple(type(x),type(y)),x!=y) ) A list whose elements are tuples with two elements, of two different types x and y. (x and y are not joined among the list elements ).