
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