[Python-ideas] positional only arguments decorator
Arnaud Delobelle
arno at marooned.org.uk
Wed May 30 17:16:43 CEST 2007
On 27 May 2007, at 16:37, Steven Bethard wrote:
> On 5/27/07, Arnaud Delobelle <arno at marooned.org.uk> wrote:
>>
>> 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'})
>
> Cool! Yes, it's hackish, but at least the results are pretty. ;-)
Wait! I think I've got a devastatingly simple solution. I've
thought about this while looking at the PyEval_EvalCodeEx() function
in Python/ceval.c. It turns out that the 'co_varnames' attribute of
a code object is only used in order to 'slot in' arguments passed as
keywords. So by adding a '@' to the names we make these arguments
positional only (one could choose any decoration including a
character which is illegal in identifiers). This method has two
advantages:
* there is **no** overhead at all in calling a positional only function.
* the function keeps its original signature (positional only
arguments are flagged with the trailing '@').
There is one 'drawback' (which could be considered a useful feature):
* if x is positional, f(**{'x@':1}) will work.
Here is the code.
------------------------------------------------------------------
from types import CodeType
code_args = (
'argcount', 'nlocals', 'stacksize', 'flags', 'code',
'consts', 'names', 'varnames', 'filename', 'name',
'firstlineno', 'lnotab', 'freevars', 'cellvars'
)
def copy_code(code_obj, **kwargs):
"Make a copy of a code object, maybe changing some attributes"
for arg in code_args:
if not kwargs.has_key(arg):
kwargs[arg] = getattr(code_obj, 'co_%s' % arg)
return CodeType(*map(kwargs.__getitem__, code_args))
def posonly(f):
code = f.func_code
varnames, nargs = code.co_varnames, code.co_argcount
varnames = ( tuple(v+'@' for v in varnames[:nargs])
+ varnames[nargs:] )
f.func_code = copy_code(code, varnames = varnames)
return f
------------------------------------------------------------------
That's it! Example:
>>> @posonly
... def update(self, container=None, **kwargs):
... return self, container, kwargs
...
>>> update(1,2, self=3, container=4, x=5)
(1, 2, {'x': 5, 'self': 3, 'container': 4})
>>> update(1)
(1, None, {})
>>> help(update) # Notice the unobfuscated signature!
Help on function update in module __main__:
update(self@, container@=None, **kwargs)
>>> # 'container' is still accessible by name:
... update(1, **{'container@':2})
(1, 2, {})
>>>
--
Arnaud
More information about the Python-ideas
mailing list