Design by Contract for Python
Carl Banks
imbosol at aerojockey.com
Wed May 14 18:21:24 EDT 2003
Terence Way wrote:
> The major problem is support for 'old' variables. Post-conditions
> should be able to compare changed values with saved values.
> I see no elegant way to do this with Daniel's approach. To do
> this with functions should require something ick, like so:
>
> def foo(self, a, b):
> blah, blah, blah
>
> def foo_pre(self, a, b):
> assert a > 5
>
> def foo_save(self, a, b):
> return [copy.copy(b)]
>
> def foo_post(self, old, ret, a, b):
> assert b == old[0] + 1
>
>
> I think a compromise approach might work: for a function foo,
> simply check if the functions _foo_pre and _foo_post are defined
> and have matching arguments ( _foo_post would have to take
> two additional arguments after the self argument). If so, these
> functions are executed as well... ANDed with the documented
> checks.
Ick. It would work, and in fact, I've written a simpleminded package
that encodes complicated relationships in the names of functions. But
I don't like it, and it turns out to be unnecessary.
Metaclasses offer a neat, cool way of grouping related functions like
preconditions and postconditions. I'll give you a bare bones example;
it should work as both a regular function and a method. (BTW, in this
example, the precondtion returns a tuple of old-values to pass into
the post-condition, rather than having a separate save function.
Also, it passes ret and old after the given args, so that you don't
have to do anything weird with self.)
class ContractMetaclass(type):
def __new__(metaclass,name,bases,clsdict):
if bases == (object,):
return type.__new__(metaclass,name,bases,clsdict)
elif bases == (Contract,):
pre = clsdict['pre']
body = clsdict['body']
post = clsdict['post']
def contract_function(*args):
saves = pre(*args)
if saves is None:
saves = ()
ret = body(*args)
post(*(args+(ret,)+saves))
return ret
return contract_function
else:
raise TypeError
class Contract(object):
__metaclass__ = ContractMetaclass
Then, you can create a function with pre- and post-conditions like
this:
class incitem0(Contract):
def pre(s):
return (s[0],)
def body(s):
s[0] += 1
return s[0]
def post(s,ret,old_s0):
assert ret == s[0] == old_s0+1
Note that the functions pre, body, and post don't take self
parameters: this particular metaclass returns an object that invokes
the three functions without a self parameter. (Metaclasses can do
that.)
The only minor problems I can see with this are an extra indentation
level, and getting used to defining a function with a class statement.
The good thing is no icky name encoding, and it forces you to keep
related functions together.
> This solves several problems with *my* stuff: the largest being
> access to closure variables (inaccessible by dynamically
> generated checking functions, but accessible by the _foo_*
> methods).
This method should likewise give you access to variables in the
surrounding scope.
My example is only bare bones: it can be improved mightily. Perhaps a
very suave version could examine the bytecode of the postcondition and
automatically save "old" values.
--
CARL BANKS
More information about the Python-list
mailing list