Welcome, guest | Sign In | My Account | Store | Cart

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.

Python, 51 lines
 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

Created by Arnaud Delobelle on Thu, 31 May 2007 (PSF)
Python recipes (4591)
Arnaud Delobelle's recipes (3)

Required Modules

  • (none specified)

Other Information and Tasks