Utility Classes

Previously...

In our last lesson, we learned how to use Python's built-in list, tuple, dict and set types in your signatures. In this section, we'll go over typecheck's notion of utility classes, which allow you to express a much larger, much more precise signature for your code.

Typechecking's Toolbox

So far, we've focused on fairly simple assertions about a function's parameters and return values. At this point, we can say that we want parameter X to be of type Y. We can also leverage Python's built-in types to create more complex type signatures. While these are certainly not insignificant, they are of fairly limited utility out in the real world. To the end of providing useful, real world-tested typechecking capabilities, we've designed a system of "utility classes" to make complex and precise assertions about the objects entering and leaving a function.

Note that all of the objects to be discussed in this section live in the main typecheck module and should be imported from there, like so:

from typecheck import IsAnyOf, IsCallable

Exact()

The Exact() utility class is used when you want to assert == equality. In the following signature, the a parameter must be either 6 or 7:

@accepts(IsOneOf(Exact(6), Exact(7)))
def foo(a):
    ...

HasAttr()

HasAttr() is used in duck-typing situations.

@accepts(HasAttr(['foo', 'bar'])

In order for an object to pass the above signature, it must have both a foo and bar attribute, regardless of what type they are.

The following signature requires that the object have foo and bar attribute, both of which must be Numbers.

@accepts(HasAttr({'foo':Number, 'bar':Number})

These two modes of expression, named and typed, can be mixed. The following signature requires that the object have a foo attribute, which may be of any type, and a bar attribute, which must be callable:

@accepts(HasAttr(['foo'], {'bar': IsCallable()})

IsAllOf()

IsAllOf() works by requiring that the object meet several different qualifications. The following signature requires that c be an instance of a class that inherits from the user-defined classes Green and Red.

@accepts(Number, String, IsAllOf(Red, Green))
def my_func(a, b, c):
    pass

The following code snippet matches the signature:

class Christmas_tree(Red, Green):
	pass

oh_tannenbaum = Christmas_tree()

my_func(3, 3, oh_tannenbaum)

Note: IsAllOf() is also available under the names And() and Intersection().

IsCallable()

IsCallable() has a very simple purpose: make sure the object can be invoked like object(). Anything that tests True with the callable() builtin will be approved by IsCallable().

@returns(IsCallable())

IsIterable()

IsIterable() is used to test that the given object can be used in for statements.

@returns(IsIterable())

IsOneOf()

IsOneOf() is used to specify that a given type must meet one of any number of qualifications.

@accepts(Sequence, String, IsOneOf(String, Number))
def my_func(a, b, c):
    pass

The following calls match this signature:

my_func(3, 3, 3) # c is an int

my_func(3, 3, 3.0) # c is a float

As we've shown in previous examples, you can use other utility classes with IsOneOf() as well:

@accepts(IsOneOf(Exact(6), Exact(7)))

Note: IsOneOf() is also available under the names Or() and Union().

Next...

In our next lesson, we'll extend what we've learned so far to cover methods of all flavours.

Valid XHTML 1.0 Transitional