Tuesday, November 26, 2013

Argument type checking

If you come to Python from a background in a programming language that provides static type checking, you could find a bit confusing, albeit liberating, not having to specify the type of the objects you are using. This could get puzzling when you deal with passing parameters to a function. What if I design a function to accept in input a number but the user misunderstand it, passing in a string instead? You could be tempted to explicitly check for the argument, and reject the call if it doesn't satisfy your requirements. This works fine, but makes the code less terse, and it is not considered very pythonesque. The preferred way is not performing any preemptive check at all, and let the code fails if it has to. You can read this as moving the responsibility of the parameters check from the actual code to its caller. When you want your function to be less rough, you can try-catch (or should I say try-except) the sensible part of you code, to convert the original exception in your preferred way of signaling an error to the caller. Consider the distance() function I have written in the previous post. It is meant to accepts as argument four numbers representing the coordinates of two points, and should give in output their distance. In case of unexpected input it would result in a TypeError exception that should be caught by the caller, or would lead to an abrupt termination of the application. We could want to manage internally to distance() these kind of problems, and let it return the no-value object (a Python None, the counterpart of a C NULL, a C++ nullptr, a Java null, ...) instead. This batch of test cases shows the new expected behavior:
import unittest


class Tests(unittest.TestCase):
    def test_ax(self):
        dist = distance('alpha', 0, 0, 0)
        self.assertIsNone(dist)

    def test_ay(self):
        dist = distance(0, 'alpha', 0, 0)
        self.assertIsNone(dist)

    def test_bx(self):
        dist = distance(0, 0, 'alpha', 0)
        self.assertIsNone(dist)

    def test_by(self):
        dist = distance(0, 0, 0, 'alpha')
        self.assertIsNone(dist)
If any input parameter is not a number, the function is expected to return None. Notice that often this behavior does not improve much our code. Before we had to worry that distance() could throw and exception, now we have to move our concern to its returned value, that could be invalid. You should at least consider if it is more appropriate to leave distance() as was before, and maybe try-catch its call, or ensure that the passed parameters are actually numbers. In any case, if we really want to do it, we could do it in a few different ways. Non-Pythonic solutions There are a couple of Python built-in functions that let us check the type of an object, type() and isinstance(). Here is how I could check if an object has type int:
if type(ax) != 'int':
    return None
What type() does is what it says, it returns the type of the passed object. It knows nothing about polymorphism, for this reason it is not the preferred alternative. It is usually better to use its more advanced sister isinstance():
if not isinstance(ax, int):
    return None
In this way we check that ay is an int, or any possible subclass of int. Our distance() function should accept int, long, and float parameters. An overload of isinstance() is available to check an object against more than one type:
if not isinstance(ax, (int, long, float)):
    return None
In this way we are checking a single parameter for being of an expected type. We should replicate the test for each of them. The Pythonian way As I suggested above, the true Pythonesque way of dealing with this issue, would probably be leaving alone distance() and introducing some checks in its caller. If we really have some good reasons to shield the original exception to the caller, we would do something like this:
import math


def distance(ax, ay, bx, by):
    try:
        return math.sqrt((bx - ax) ** 2 + (by - ay) ** 2)
    except TypeError:
        return None
Just execute the original code. If it throws an exception, catch it and do what you want to do instead. Clean code, no extra price the caller should pay for checks, only the offending calls would result in the extra-slow management required to generate an exception in math.sqrt(), catching it in distance() and converting it to the expected behavior.

Unit test for Python

As a sort of hello world for Python (2.7.5+), I write here a function that calculate the distance between two points in a Cartesian plane. However, the most interesting part of the post is seeing how easy (and fun) is to integrate the code with unittest, the official Python module that implements unit testing framework, also known as PyUnit. I firstly write a minimal version for my function, that actually works fine in the case the segment that I want to measure has collapsed to a single point:
def distance(ax, ay, bx, by):
    return 0.0
If you haven't seen any Python code before, you would probably appreciate its syntheticity. No need of specifying variable (and function) type, no need of much more than the strict necessity. Notice the colon after the function prototype, that says its body is about to start, and notice the mandatory indentation (four blanks is its traditional span) for the code owned by the block. Writing test cases for this function, requires us to import the module unittest and define a class that derives from its TestCase class. Besides, I need also to import the math module, so that I could use its sqrt() function. Finally we just need to add a new method for each test case we want to create:
import unittest
import math


class Tests(unittest.TestCase):
    def test_zero(self):
        dist = distance(0, 0, 0, 0)
        self.assertEqual(0, dist)

    def test_sqr2(self):
        dist = distance(0, 0, 1, 1)
        self.assertEqual(math.sqrt(2), dist)

    def test_sqr8(self):
        dist = distance(0, 0, 2, 2)
        self.assertEqual(math.sqrt(8), dist)
Each test case calls my distance() function, and then asserts that its result has the expected value. Running the test, you should get a success, test_zero, and two failures. Now it is easy to refactor the function to get a better result:
import math


def distance(ax, ay, bx, by):
    return math.sqrt((bx - ax) ** 2 + (by - ay) ** 2)
As you can see, double-star is the Python operator square. The usual precedence rules apply for mathematical operators. Given this distance() implementation, you should get a full success for the above tests.