[Python-ideas] Optional static typing -- late to the party
Andrea Censi
censi at mit.edu
Wed Aug 20 14:56:38 CEST 2014
On Wed, Aug 20, 2014 at 5:08 AM, Tennessee Leeuwenburg
<tleeuwenburg at gmail.com> wrote:
>
> I was led to a conclusion: what is wrong with allowing both? Indeed, clearly
> both are actually already supported by various tools. So perhaps there is
> actually a higher-level common concept -- what are the actual assertion
> which are going to be supported? Can I declare a variable only to be a list,
> or can it be a list-of-ints?
Yes - why not both? or all three ways?
Here's the "nonempty list of positive int" example done
using PyContracts [1] in 3 ways:
1) Using decorators;
2) Using annotations (Python 3 only);
3) Using docstrings.
The type "list[length-type](entry-type)" means a list
where the length satisfies length-type and the entries
satisfy entry-type. So "list[>=1](int,>0)" means
a list of length at least 1 whose entries are positive integers.
1) Using decorators:
@contract(l='list[>=1](int,>0)', returns='int,>0')
def mysum(l):
...
2) Using annotations:
@contract
def mysum(l: 'list[>=1](int,>0)') -> 'int,>0':
...
3) Using docstrings, with the :type: and :rtype: tags:
@contract
def mysum(l):
"""
:type l: list[>=1](int,>0)
:rtype: int,>0
"""
...
If using (1) and (2), a docstring like in (3) is automatically generated.
[1]: https://pypi.python.org/pypi/PyContracts
Steven D'Aprano says:
> docstring annotations have the disadvantage of being
text only, instead of expressions which evaluate to arbitrarily powerful
or useful objects.
I disagree, it's actually the other way around: Python's general
purpose syntax is actually cumbersome with respect to a custom
language for types.
Once you have the freedom of using a custom language (e.g. using
PyParsing) then you can create very compact and at the same time
powerful contracts.
For example, expressing a type such as "list[>=1](int,>0)" using a
valid Python expression will take much more space.
And you have the ability of creating special syntax where it makes
sense, for example in Numpy --- look at this definition of matrix
multiplication with compatible dimensions:
@contract
def matrix_multiply(a, b):
''' Multiplies two matrices together.
:param a: The first matrix. Must be a 2D array.
:type a: array[MxN],M>0,N>0
:param b: The second matrix. Must be of compatible dimensions.
:type b: array[NxP],P>0
:rtype: array[MxP]
'''
return numpy.dot(a, b)
If the contract is violated, there will be a nice message saying
exactly what's wrong
Example:
a = numpy.zeros((2,2))
b = numpy.zeros((3,2))
matrix_multiply(a,b)
Exception:
Breach for argument 'b' to matrix_multiply().
Expected value for 'N' was: Instance of int: 2
instead I received: Instance of int: 3
checking: N for value: Instance of int: 3
checking: NxP for value: Instance of tuple: (3, 2)
checking: array[NxP] for value: array['3x2'](float64)
array([[ 0., 0.], [ 0., 0.], ... [clip]
checking: array[NxP],P>0 for value: array['3x2'](float64)
array([[ 0., 0.], [ 0., 0.], ... [clip]
best,
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