[Python-ideas] type checking using PyContracts; was: Proposal: Use mypy syntax for function annotations

Andrea Censi censi at mit.edu
Tue Aug 19 19:49:37 CEST 2014


Dear all,

(Apologies for not replying to the original thread,  I only signed up
to the mailing list today)

I wrote PyContracts[1], which implements type-checking functionality
using decorators for Python 2.7+ and annotations in Python 3.

 [1] http://andreacensi.github.com/contracts/

> def word_count(input: List[str]) -> Dict[str, int]:

Here's that example done using PyContracts and Python 3,
and note the extra constraint '>=1' that PyContract can express:

    from contracts import contract

    @contract
    def word_count(input:'list(str)') -> 'dict(str:int,>=1):
        ...

Here's that example done using PyContracts and Python 2.7, using
a decorator instead of annotations:

    from contracts import contract

    @contract(input='list(str)', returns='dict(str:int,>=1)')
    def word_count(input):
        ...

I would like to make a few points from my experience of writing
PyContracts regarding the general issues that one encounters in adding
type checking to Python.

1) Once one starts to think about it, "types" are very broad and if you
   design a system from scratch, make sure it is extensible. For example,
   you might want to have "positive integer" in addition to "integer"
   ('int', and 'int,>=0' in PyContracts). Also, let the users add
their own types.

2) Dependent types are a must and they must have a simple syntax.
   For example, this is a PyContracts annotation that says the function takes
   a list of strings and returns a list of integers of the same size N:

       @contract(x='list[N](str)', returns='list[N](int)')
       def f(x):
           return list(map(len, x))

   PyContract's convention is that and a,b,c,...,y,z bind to any variables
   and A,B,C,...,Z bind to integer variables. This makes numpy contracts
   very nice looking:

       @contract
       def gray2rgb(gray:'array[HxW](uint8)') -> 'array[HxWx3](uint8)':
         ...

3) There is an argument for using regular strings to describe annotations
   rather than Python constructs such as MyPy. One advantage
   is that the system is backwards compatible. It also makes the code
   much cleaner because you don't need to import the various symbols.

   Here's a simple example in MyPy:

        from typing typevar, Sequence

        T = typevar('T')      # Declare type variable

        def first(seq: Sequence[T]) -> T:
            return seq[0]

   Here's the same example in PyContracts (using Python 3's annotations):

        from contracts import contract

        @contract
        def first(s: 'seq(type(t))') -> 'type(t)':
            return s[0]

   Same example in Python 2.7+ (using decorators):

     from contracts import contract

        @contract(s='seq(type(t))', returns='type(t)')
        def first(s):
            return s[0]

(By the way, after a while, I actually like the decorator version
 better than Python 3's annotations, because you can see very
easily the function's name and parameters, and only if you want
you can then read the function annotations above.)

cheers,
A.

-- 
Andrea Censi | http://censi.mit.edu | "Not all those who wander are lost."
research scientist @ LIDS / Massachusetts Institute of Technology


More information about the Python-ideas mailing list