[Python-ideas] positional only arguments decorator

Arnaud Delobelle arno at marooned.org.uk
Sun May 27 11:03:14 CEST 2007


On 21 May 2007, at 19:30, Steven Bethard wrote:

> Ok, looks like there's not much chance of agreeing on a syntax, so
> here's a decorator that covers the two use cases I know of::
>
> * matching the signature of dict() and dict.update()
> * allowing arguments to have their names changed without worrying
> about backwards compatibility (e.g. ``def func(sequence)`` changing to
> ``def func(iterable)``)

I propose a slightly different solution.  It may be a bit of a hack,  
but I don't see why it should not be safe. This solution allows you  
to use * and ** in order to write a definition like:

 >>> @posonly
... def update(self, container=None, **kwargs):
...     return self, container, kwargs
...
 >>> update('self')
('self', None, {})
 >>> update('self', 'container')
('self', 'container', {})
 >>> update('self', self='abc', container='xyz', foo='bar')
('self', None, {'self': 'abc', 'foo': 'bar', 'container': 'xyz'})
 >>>

Or:

 >>> @posonly
... def foo(x, y=1, *args, **kwargs):
...     return x, y, args, kwargs
...
 >>> foo('hey')
('hey', 1, (), {})
 >>> foo(*'spam')
('s', 'p', ('a', 'm'), {})
 >>> foo(577, x=314, args=1618, kwargs=2718)
(577, 1, (), {'x': 314, 'args': 1618, 'kwargs': 2718})
 >>>

Without * or ** it gives the following behaviour:

 >>> @posonly
... def f(abc, xyz=42):
...     return abc, xyz
...
 >>> f(3)
(3, 42)
 >>> f(4, 5)
(4, 5)
 >>> f(abc=3)
Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "qrg.py", line 147, in posf
     raise TypeError('posonly function %s() got keyword argument'
TypeError: posonly function f() got keyword argument
 >>> f()
Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "qrg.py", line 152, in posf
     return flat_f(*args, **kwargs)
TypeError: f() takes at least 1 argument (0 given)
 >>>

This is achieved by 'flattening' the function first.  Here is the code:

import new
import inspect

def flattened(f):
     """Changes a function f(...[, *y] [, **z])
        to f(...[, y=None] [, z=None])"""
     fc = f.func_code
     defaults = f.func_defaults
     flags = fc.co_flags
     argcount = fc.co_argcount
     has_varargs = flags & 4
     if has_varargs:
         flags ^= 4
         argcount += 1
         if defaults: defaults += (None,)
     has_varkwargs = flags & 8
     if has_varkwargs:
         flags ^= 8
         argcount += 1
         if defaults: defaults += (None,)
     flat_code = new.code(argcount, fc.co_nlocals, fc.co_stacksize,  
flags,
         fc.co_code, fc.co_consts, fc.co_names, fc.co_varnames,
         fc.co_filename, fc.co_name, fc.co_firstlineno, fc.co_lnotab)
     flat_f = new.function(flat_code, f.func_globals, f.func_name,  
defaults,
                           f.func_closure)
     return flat_f


def posonly(f):
     "posonly(f) makes the arguments of f positional only"
     f_args, f_varargs, f_varkwargs, f_defaults = inspect.getargspec(f)
     f_nargs = len(f_args)
     f_name = f.__name__
     flat_f = flattened(f)
     def posf(*args, **kwargs):
         if f_varkwargs:
             kwargs = {f_varkwargs: kwargs}
         elif kwargs:
             raise TypeError('posonly function %s() got keyword  
argument'
                             % f_name)
         if f_varargs:
             kwargs[f_varargs] = args[f_nargs:]
             args = args[:f_nargs]
         return flat_f(*args, **kwargs)
     posf.__name__ = f_name
     return posf

-- 
Arnaud





More information about the Python-ideas mailing list