[Python-ideas] Explicit variable capture list

Ethan Furman ethan at stoneleaf.us
Tue Jan 26 13:55:31 EST 2016


On 01/26/2016 08:55 AM, Ethan Furman wrote:

> Currently, something not too ugly would be to use descriptors --
> something like:
>
> from dbc import require, ensure
>
> class Frobnigate(object):
>      @require
>      def spammer(self, desc):
>          desc.assertInRange(arg1, 0, 99)
>
>      @spammer
>      def _spammer(self, arg1, arg2):
>          return arg1 // arg2 + arg1
>
>      @spammer.ensure
>      def spammer(self, desc, res):
>          if desc.arg2 % 2 == 1:
>              desc.assertEqual(res % 2, 1)
>          else:
>              desc.assertEqual(res % 2, 0)
>
>      @ensure
>      def egger(self, desc, res):
>          desc.assertIsType(res, str)
>
>      @egger
>      def _egger(self, egg_type):
>          'scrambled, poached, boiled, etc'
>           return egg_type
>
> Where 'desc' in the above code is 'self' for the descriptor so saved
> arguments could be accessed, etc.
>
> I put a leading underscore on the body so it could be kept separate and
> more easily subclassed without losing the DBC portions.
>
> If 'require' is not needed, one can use 'ensure'; both create the DBC
> object which would take care of calling any/all requires, then the
> function, then any/all ensures, and also grabbing and saving the
> function signature and actual parameters.

The descriptor itself might look like:

# untested
class require:

     def __init__(desc, func=None):
         desc.require = []
         desc.ensure = []
         desc.name = None
         desc.func = None

     def __call__(desc, func):
         # desc.func is not None, func is the actual function,
         # otherwise it's a requires function
         if desc.func is None:
             self.require.append(func)
             return desc
         else:
             desc.func_name = name = func.__name__
             if name.startswith('_'):
                 name = name[1:]
             desc.name = name
             return func

     def __get__(desc, self, cls):
         function = self.getattr(desc.func_name)
         def caller(self, *args, **kwds):
             for require in desc.require:
                 require(self, desc, *args, **kwds)
             res = function(self, *args, **kwds)
             for ensure in desc.ensure:
                 ensure(self, desc, res, *args, **kwds)
             return res
         return caller

     def ensure(desc, func):
         self.ensure.append(func)
         return desc

     def require(desc, func):
         self.require.append(func)
         return desc


I decided to pass args and kwds rather than save them to the descriptor 
instance, hoping threading would be easier that way.

The 'ensure' class would be very similar.

This style does require the programmer to have both names: 'spammer' and 
'_spammer' -- it would be a bit cleaner to have a metaclass with a 
custom __getattribute__, but a lot more work and possible metaclass 
conflicts when combining with other interesting metaclasses.

--
~Ethan~


More information about the Python-ideas mailing list