[Python-ideas] Explicit variable capture list

Sjoerd Job Postmus sjoerdjob at sjec.nl
Tue Jan 26 05:47:06 EST 2016


On Tue, Jan 26, 2016 at 10:34:55AM +1100, Steven D'Aprano wrote:
> On Wed, Jan 20, 2016 at 05:04:21PM -0800, Guido van Rossum wrote:
> > On Wed, Jan 20, 2016 at 4:10 PM, Steven D'Aprano <steve at pearwood.info>
> > wrote:
> [...]
> > > (I'm saving my energy for Eiffel-like require/ensure blocks
> > > *wink*).
> > >
> > 
> > Now you're making me curious.
> 
> 
> Okay, just to satisfy your curiosity, and not as a concrete proposal at 
> this time, here is a sketch of the sort of thing Eiffel uses for Design 
> By Contract.
> 
> Each function or method has an (optional, but recommended) pre-condition 
> and post-condition. Using a hybrid Eiffel/Python syntax, here is a toy 
> example:
> 
> class Lunch:
>     def __init__(self, arg):
>         self.meat = self.spam(arg)
> 
>     def spam(self, n:int=5):
>         """Set the lunch meat to n servings of spam."""
>         require:
>             # Assert the pre-conditions of the method.
>             assert n >= 1
>         ensure:
>             # Assert the post-conditions of the method.
>             assert self.meat.startswith('Spam')
>             if ' ' in self.meat:
>                 assert ' spam' in self.meat
>         # main body of the method, as usual
>         serves = ['spam']*n
>         serves[0] = serves.title()
>         self.meat = ' '.join(serves)
> 
> 
> The require block runs before the body of the method, and the ensure 
> block runs after the body, but before the method returns to the caller. 
> If either fail their assertions, the method fails and raises an 
> exception.
> 
> 
> Benefits:
> 
> - The pre- and post-conditions make up (part of) the method's
>   contract, which is part of the executable documentation of 
>   the method. Documentation tools can extract the ensure 
>   and require sections as present them as part of the API docs.
> 
> - The compiler can turn the contract checking on or off as
>   needed, with the ensure/require sections handled independently.
> 
> - Testing pre- and post-conditions is logically separate from 
>   the method's implementation. This allows the implementation 
>   to vary while keeping the contract the same.
> 
> - But at the same time, the contract is right there with the 
>   method, not seperated in some potentially distant part of the
>   code base.

One thing I immediately thought of was using decorators.

    def requires(*conditions):
        def decorator(func):
            # TODO: Do some hackery such that the signature of wrapper
            # matches the signature of `func`.
            def wrapper(*args, **kwargs):
                for condition in conditions
                    assert eval(condition, {}, locals())
                return func(*args, **kwargs)
            return wrapper
        return decorator

    def ensure(*conditions):
        def decorator(func):
            def wrapper(*args, **kwargs):
                try:
                    return func(*args, **kwargs)
                finally:
                    for condition in conditions:
                        assert eval(condition, {}, locals())
        return decorator

Maybe do some checking for the optimization-level flag, and replace the
decorator function with `return func` instead of another wrapper?

The `ensure` part isn't quite to my liking yet, but I think that the
`ensure` should have no need to access internal variables of the
function, but only the externally visible state.

(This somewhat mimics what I'm trying to fiddle around with in my own
time: writing a decorator that does run-time checking of argument and
return types of functions.)


More information about the Python-ideas mailing list