Using Python's Built-in Types
Previously...
In our last lesson, we learned the bare basics of using the typecheck package.
In this section, we'll discuss how to use Python's built-in list, tuple and dict types in your signatures.
Dicts
Dictionaries are the most straight-forward built-in type to use in typechecking. They're very easy to declare: you just provide a type for keys and a type for values:
@accepts(String, Number, {str: Number})
This will assert that all the keys in the dictionary are strs and all values are Numbers.
As with all typing expressions in typecheck, dicts can be nested:
@returns({str: {float: Number}})
Recall that you've seen this notation before: we used it in lesson 1 when talking about the slurpy **kwargs parameter.
Notes:
Empty dictionaries will always match, no matter what keys/values types have been declared. For example,
{}would match both the signatures{str: Number}and{str: {float: Number}}.To declare that you don't care about the types of a dictionary's keys or values, simply pass the
dicttype:@accepts(dict)
Lists
Like with dicts, you use the standard list notation to declare that an object should be a list.
The following signature asserts that the function takes a list of Numbers and returns a single Number.
@accepts([Number]) @returns(Number) def sum(num_list): accumulator = 0 for i in num_list: accumulator += i return accumulator
It is also possible to declare that the elements in a list should follow a pattern.
In this next signature, the list is expected to be of alternating Numbers and Strings.
We'll also provide examples of lists that match this signature, as well as lists that fail against it.
@accepts([Number, String])
# Matches [5, 'foo'] # Matches, repeating [5, 'foo', 6, 'bar'] # Fails; "6" is not an int [5, 'foo', "6", 'bar'] # Fails; the list does not complete the pattern [5, 'foo', 6]
This last example is important; for a list to match the pattern, it must complete the pattern.
Like dictionaries, lists can be nested in signatures as well.
The following signature requires a list whose elements alternate between lists of Numbers and lists of Strings:
@accepts([[Number], [String]])
This structure matches the signature:
[ [5, 6, 7], ['foo', 'bar', 'baz'] ]
All the items in the "Notes" section for dictionaries apply to lists.
Tuples
Tuples are used a bit differently in the typecheck world than they are in the rest of Python.
Whereas mainstream Python uses tuples simply as immutable lists, we treat them in a manner closer to their mathematical usage.
Unlike typechecking dictionaries and lists, where you only care about the type of the object's contents, tuples have a size associated with them. Let's see if a few examples make this clearer:
@accepts( (Number, String) )
This specifies that the function accepts a 2-tuple; the first element must be a Number, with the second element being a String.
Unlike lists, tuples don't repeat; you can't pass (5, 'foo', 6, 'bar') to the above signature.
Also, unlike both lists and dictionaries, an empty tuple doesn't match a non-empty tuple. Try it.
>>> @accepts( (int, str) ) ... def foo(a): ... pass ... >>> foo(()) Traceback (most recent call last): [snip] typecheck.TypeCheckError: Argument a: for (), \ expected (<type 'int'>, <type 'float'>), got () >>>
As you might expect, tuples can be nested. See if you can figure out some data structures that would match this signature:
@accepts( (Number, ((String, String), (Number, float))) )
typecheck also supports another form of tuples, the automatically unpacked tuples in Python functions:
@typecheck(Number, (Number, Number))
def my_func(a, (b, c)):
pass
Sets
typecheck also includes support for one of Python's newer built-in types, set.
Before using sets in signatures, however, you need to tell typecheck to activate this feature:
import typecheck.sets
Now, let's build a signature that will check for a set of Numbers:
@returns( set([Number]) )
That's all there is. typecheck sticks to the native set syntax, so there's nothing new to learn on that front.
We can also allow a set to have members of multiple types.
Here, we check for a set whose members can be Numbers or Strings:
@returns( set([Number, String]) )
Putting It All Together
All of the above types can be used in combination with one another. Observe:
@accepts( {(Number, Number): String} )
@returns( [(Number, Number), (Number, Mapping)] )
@returns( ([Number], [Number], {String: String}) )
@accepts( set([ (Number, Number) ]) )
Next...
In our next lesson, we'll introduce typecheck's library of utility classes, a collection of tools designed to make writing powerful type signatures easier than ever.