Sometimes it is useful to make sure that arguments in a function are positional only, i.e. cannot be passed as keywords. This recipe defines a decorator 'posonly' that does this: arguments cannot be passed as keywords. Note that **kwargs can still be used in the function definition to accept keywords.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | 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):
"Make the arguments of a function positional only"
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
#--- Examples of use ---------------------
>>> @posonly
... def f(x, y=1): return x, y
...
>>> f(1)
(1, 1)
>>> f(1, y=2)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: f() got an unexpected keyword argument 'y'
>>> f(x=1)
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: f() got an unexpected keyword argument 'x'
>>> @posonly
... def update(self, container=None, **kwargs):
... "Pretend function"
... return self, container, kwargs
...
>>> # self and container can be used as keyword argument names
... update('self', 'container', self=1, container=2)
('self', 'container', {'self': 1, 'container': 2})
>>> # There is still a way to access posonly args by name...
... f(**{'x@':'spam'})
('spam', 1)
>>>
|
This follows from a discussion on the python-dev then python-ideas mailing lists:
http://mail.python.org/pipermail/python-dev/2006-May/064790.html http://mail.python.org/pipermail/python-ideas/2007-May/000704.html
It works by adding a '@' at the end of the argument names in f.func_code.co_varnames. Hence, as shown above, it is still possible to set arguments by keyword, albeit only through the **{..} construct.
There are other implementations in this thread:
http://mail.python.org/pipermail/python-ideas/2007-May/000763.html