programming by contract using "decorators"

Russ uymqlp502 at sneakemail.com
Mon Sep 3 22:43:49 EDT 2007


In the thread I started a few days ago, I was told that "programming
by contract," could be done with "decorators." I was skeptical that
this was a good approach, but as an exercise, I tried to code it up in
a reasonably elegant form. I'd like to think I succeeded -- and I must
admit that those who told me it could be done with decorators were
right.

My code is shown below. It shows a function called "myfunction," and
another function for testing it called "myfunction_test." More
detailed explanation follows the listing. Comments and feedback are
welcome, of course.

#!/usr/bin/env python

def contract(test, check=0):
    "enforce programming by contract"

    if check: return test

    def null(func):
        def null2(*args, **keys):
            return func(*args, **keys)
        return null2

    return null

def getarg(args, keys, num, name):

    if name in keys: return keys[name]
    return args[num]

def myfunction_test(myfunction):
    "contract test for function myfunction"

    def test(*args, **keys):

        x = getarg(args, keys, 0, 'x')
        y = getarg(args, keys, 1, 'y')
        z = getarg(args, keys, 2, 'z')

        # preconditions would go here, if there were any

        result = myfunction(x, y, z) # execute function

        assert result == x**2 + 3 * y + z # post-condition

        return result

    return test

#=======================================

CheckContracts = 1

@contract(myfunction_test, CheckContracts)

def myfunction(x, y, z): return x**2 + 3 * y + z

print myfunction(4, z=1, y=3)

------------------- end of listing ---------------------

Here's what is going on. At the bottom of the listing, you will see
the definition of a function called "myfunction," followed by a call
of the function. It's a trivial function, but I gave it three
arguments just to test passing them out of order by keyword.

Just above the definition of "myfunction" is a "decorator" called
"contract," which takes two arguments. The first argument specifies
the name of the self-test contract function, which is
"myfunction_test" in this case. The second argument is used to enable
or disable the contract tests.

Note that I could have just called the decorator "myfunction_test" and
omitted the "contract" decorator, but I think this form, although
slightly less efficient, is more readable. It also allows the enabling/
disabling logic to be put in one function rather than having to repeat
it in every contract test function.

Dealing with the arguments was not a trivial matter -- at least not
for me. I had to experiment a bit to get it right. The "getarg"
function is simply a utility for parsing the ordered and keyword
arguments. It's very simple, but if something like this already
exists, please let me know. Thanks.




More information about the Python-list mailing list