[Python-ideas] DBC (Re: Explicit variable capture list)

Andrew Barnert abarnert at yahoo.com
Tue Jan 26 17:10:41 EST 2016


There are probably a dozen DBC packages on PyPI, and dozens more that never even got that far. If this is doable without language changes, surely it'll be done on PyPI first, get traction there, and only then be considered for inclusion in the stdlib (so that it can be used to contractify parts of the stdlib), right?

But, since this is fun:

On Jan 26, 2016, at 07:24, Chris Angelico <rosuav at gmail.com> wrote:
> 
>> On Wed, Jan 27, 2016 at 2:06 AM, Paul Moore <p.f.moore at gmail.com> wrote:
>> Well, classes can be callable already, so how about
>> 
>> @DBC
>> class myfunction:
>>    def __call__(self, args):
>>        ...
>>    @precondition
>>    def requires(self):
>>        ...
>>    @postcondition
>>    def ensures(self, result):
>>        ...
>> 
>> The DBC class decorator does something like
>> 
>> def DBC(cls):
>>    def wrapper(*args, **kw):
>>        fn = cls()
>>        fn.args = args
>>        fn.kw = kw
>>        for pre in fn.__preconditions__:
>>            pre()
>>        result = fn(*args, **kw)
>>        for post in fn.__postconditions__:
>>            post(result)
>>    return wrapper
>> 
>> Pre and post conditions can access the args via self.args and self.kw.
>> The method decorators would let you have multiple pre- and
>> post-conditions. Or you could use "magic" names and omit the
>> decorators.
> 
> I'd rather use magic names - something like:
> 
> @DBC
> class myfunction:
>    def body(self, args):
>        ...
>    def requires(self):
>        ...
>    def ensures(self, result):
>        ...
> 
> and then the DBC decorator can create a __call__ method. This still
> has one nasty problem though: the requires and ensures functions can't
> see function arguments. You could get around this by duplicating the
> argument list onto the other two, but who wants to do that?

You could do this pretty easily with a macro that returns (the AST for) something like this:

    def myfunction([func.body.params]):
        [func.requires.body]
        try:
            return_to_raise([func.body.body])
        except Return as r:
            result, exc = r.args(0), None
            [func.ensures.body]
            return result
        except Exception as exc:
            [func.ensures.body]
            raise

(I deliberately didn't write this in MacroPy style, but obviously if you really wanted to implement this, that's how you'd do it.)

There are still a few things missing here. For example, many postconditions are specified in terms of the pre- and post- values of mutable parameters, with self as a very important special case. And fitting class invariant testing into this scheme should be extra fun. But I think it's all doable.


More information about the Python-ideas mailing list