Forced naming of parameters in Python
Solution 1:
In Python 3 - Yes, you can specify *
in the argument list.
From docs:
Parameters after “*” or “*identifier” are keyword-only parameters and may only be passed used keyword arguments.
>>> def foo(pos, *, forcenamed):
... print(pos, forcenamed)
...
>>> foo(pos=10, forcenamed=20)
10 20
>>> foo(10, forcenamed=20)
10 20
>>> foo(10, 20)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() takes exactly 1 positional argument (2 given)
This can also be combined with **kwargs
:
def foo(pos, *, forcenamed, **kwargs):
To complete example:
def foo(pos, *, forcenamed ):
print(pos, forcenamed)
foo(pos=10, forcenamed=20)
foo(10, forcenamed=20)
# basically you always have to give the value!
foo(10)
output:
Traceback (most recent call last):
File "/Users/brando/anaconda3/envs/metalearning/lib/python3.9/site-packages/IPython/core/interactiveshell.py", line 3444, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-12-ab74191b3e9e>", line 7, in <module>
foo(10)
TypeError: foo() missing 1 required keyword-only argument: 'forcenamed'
So you are forced to always give the value. If you don't call it you don't have to do anything else named argument forced.
Solution 2:
You can force people to use keyword arguments in Python3 by defining a function in the following way.
def foo(*, arg0="default0", arg1="default1", arg2="default2"):
pass
By making the first argument a positional argument with no name you force everyone who calls the function to use the keyword arguments which is what I think you were asking about. In Python2 the only way to do this is to define a function like this
def foo(**kwargs):
pass
That'll force the caller to use kwargs but this isn't that great of a solution as you'd then have to put a check to only accept the argument that you need.
Solution 3:
True, most programming languages make parameter order part of the function call contract, but this doesn't need to be so. Why would it? My understanding of the question is, then, if Python is any different to other programming languages in this respect. In addition to other good answers for Python 2, please consider the following:
__named_only_start = object()
def info(param1,param2,param3,_p=__named_only_start,spacing=10,collapse=1):
if _p is not __named_only_start:
raise TypeError("info() takes at most 3 positional arguments")
return str(param1+param2+param3) +"-"+ str(spacing) +"-"+ str(collapse)
The only way a caller would be able to provide arguments spacing
and collapse
positionally (without an exception) would be:
info(arg1, arg2, arg3, module.__named_only_start, 11, 2)
The convention of not using private elements belonging to other modules already is very basic in Python. As with Python itself, this convention for parameters would only be semi-enforced.
Otherwise, calls would need to be of the form:
info(arg1, arg2, arg3, spacing=11, collapse=2)
A call
info(arg1, arg2, arg3, 11, 2)
would assign value 11 to parameter _p
and an exception risen by the function's first instruction.
Characteristics:
- Parameters before
_p=__named_only_start
are admitted positionally (or by name). - Parameters after
_p=__named_only_start
must be provided by name only (unless knowledge about the special sentinel object__named_only_start
is obtained and used).
Pros:
- Parameters are explicit in number and meaning (the later if good names are also chosen, of course).
- If the sentinel is specified as first parameter, then all arguments need to be specified by name.
- When calling the function, it's possible to switch to positional mode by using the sentinel object
__named_only_start
in the corresponding position. - A better performance than other alternatives can be anticipated.
Cons:
Checking occurs during run-time, not compile-time.- Use of an extra parameter (though not argument) and an additional check. Small performance degradation respect to regular functions.
- Functionality is a hack without direct support by the language (see note below).
- When calling the function, it's possible to switch to positional mode by using the sentinel object
__named_only_start
in the right position. Yes, this can also be seen as a pro.
Please do keep in mind that this answer is only valid for Python 2. Python 3 implements the similar, but very elegant, language-supported mechanism described in other answers.
I've found that when I open my mind and think about it, no question or other's decision seems stupid, dumb, or just silly. Quite on the contrary: I typically learn a lot.