In Python, positional arguments must come before keyword arguments – it’s just a rule of the language. That said, there are a few ways (that I wouldn’t necessarily recommend) that you could write a function to get around this.
This is essentially how the built-in range
function does what you’re describing:
def range(*args):
# default values
start = 0
step = 1
n_args = len(args)
if not 0 < n_args < 4:
raise TypeError(f'range expected 1 arguments, got {n_args}')
elif n_args == 1:
stop = args[0]
elif n_args == 2:
start, stop = args
else:
start, stop, step = args
# etc...
It requires arguments be passed in a specific order, then determines what variables to assign values to based on the number of arguments passed.
Or, consider the following:
def example(a=10, b=None):
assert b is not None, "argument 'b' is required"
# etc...
This is an even uglier solution, because:
- you must pass
b
as a keyword argument, not a positional argument - The function signature doesn’t give the user any indication that
b
is required – it looks like it’s optional, but the function will fail if it’s not explicitly passed. - You’d have to set the parameter’s “invalid” value to something you’re 100% certain no one would ever want to actually pass to that argument.
Unless there’s a really compelling reason not to, it’s best to stick to the usual format (positional arguments first, optionally followed by collecting leftover *args
, then keyword arguments, optionally followed by collecting leftover **kwargs
). It’s how people expect to use a function and will make your code easier to both use and read. If there really is a compelling reason to deviate from that, mimic the setup of range
.
edit:
re: your question about “correcting” the function signature to show certain parameter names instead of *args
, the example above massively simplifies how the range
built-in works. In reality, it’s implemented in C and its signature/docstring are set here. It’s more analogous to a class that defines a __call__
method that gets run inside the constructor than a regular function.
Manually overriding an object’s signature would be tricky (not to mention a whole lot of extra steps to avoid using the “normal” parameter order) — you’re much better off just putting the “correct” parameter names in the docstring.
There are some really, really hacky ways you could do it. E.g., you could wrap the function in a decorator that uses something like inspect.Signature
to modify the function object. Or maybe you could convert it to a class, have it inherit from some parent class, then use a combination of __init_subclass__
in the parent and __new__
in the child to intercept and modify the arguments passed to the constructor. But these solutions are so far off in left field that it’s worth asking why there isn’t a simpler way to solve this problem. In my opinion, it’s because this simply isn’t something you want to be doing in the first place.
CLICK HERE to find out more related problems solutions.