[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