Force parameter naming in Python

In Python, you can have a function definition:

def info(object, spacing=10, collapse=1) 

which could be called in any of the following ways:

 info(odbchelper) info(odbchelper, 12) info(odbchelper, collapse=0) info(spacing=15, object=odbchelper) 

thanks to Python allowing arguments of any order if named.

The problem we are facing is related to the expansion of some of our great functions, people can add parameters between spacing and collapse , which means that incorrect values ​​may have parameters that are not called. In addition, sometimes it is not always clear what needs to be done. We will force people to name certain parameters, not just the coding standard, but ideally is it a plugin or a pydev plugin?

so in the examples above only the last one will check when all parameters are specified.

Most likely, we will include it only for certain functions, but any suggestions on how to implement this, or if it is even possible, will be appreciated.

+71
function python parameter-passing coding-style
Jun 03 '10 at 11:00
source share
11 answers

In Python 3 - Yes, you can specify * in the argument list.

From the docs :

Parameters after "*" or "* identifier" are parameters only for keywords and can only be passed using 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): 
+147
Jan 12 '13 at 23:18
source share

You can get people to use keyword arguments in Python3 by defining a function as follows.

 def foo(*, arg0="default0", arg1="default1", arg2="default2"): pass 

Having made the first argument a positional argument without a name, you force everyone who calls the function to use the keyword arguments, which 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 

This will force the caller to use kwargs, but it’s not so much to solve, since you would have to check to accept only the argument that you need.

+24
Aug 21 2018-12-12T00:
source share

True, most programming languages ​​form part of the parameter order in the function call contract, but this is not necessarily the case. Why? My understanding of the question is that if Python is different in this respect from other programming languages. 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 the caller could provide the spacing and collapse arguments positionally (without exception):

 info(arg1, arg2, arg3, module.__named_only_start, 11, 2) 

The convention of using private elements belonging to other modules is already basic in Python. As with Python itself, this convention for parameters will only be semi-referenced.

Otherwise, calls should be of the form:

 info(arg1, arg2, arg3, spacing=11, collapse=2) 

Call

 info(arg1, arg2, arg3, 11, 2) 

assigns the value 11 to the _p parameter and the exception raised by the first instruction of the function.

Specifications:

  • Parameters up to _p=__named_only_start are allowed positionally (or by name).
  • Parameters after _p=__named_only_start should be provided only by name (unless knowledge and use of the special sentinel object __named_only_start ).

Pros:

  • The parameters are explicit in quantity and value (later, if, of course, good names are chosen).
  • If the master is specified as the first parameter, then all arguments must be specified by name.
  • When calling the function, you can switch to positional mode using the sentinel __named_only_start in the corresponding position.
  • Higher performance is expected than other alternatives.

Minuses:

  • Validation occurs at run time, not compile time.
  • Using an additional parameter (although not an argument) and an additional check. Slight performance degradation with respect to regular functions.
  • Functionality is hacking without direct language support (see note below).
  • When calling the function, you can switch to positional mode using the __named_only_start interrogating __named_only_start in the right position. Yes, it can also be regarded as a professional.

Please keep in mind that this answer is only applicable to Python 2. Python 3 implements a similar, but very elegant, language-supported mechanism described in other answers.

I found that when I open my mind and think about it, no doubt or other solution seems stupid, stupid, or just plain stupid. On the contrary: I usually learn a lot.

+9
Jul 02 '13 at 20:35
source share

You can do this in a way that works in both Python 2 and Python 3 by making a “dummy” argument to the first keyword with a default value that will not be “natural”. This keyword argument may be preceded by one or more arguments without a value:

 _dummy = object() def info(object, _kw=_dummy, spacing=10, collapse=1): if _kw is not _dummy: raise TypeError("info() takes 1 positional argument but at least 2 were given") 

This will allow:

 info(odbchelper) info(odbchelper, collapse=0) info(spacing=15, object=odbchelper) 

but not:

 info(odbchelper, 12) 

If you change the function to:

 def info(_kw=_dummy, spacing=10, collapse=1): 

then all arguments must have keywords, and info(odbchelper) will no longer work.

This will allow you to place additional keyword arguments anywhere after _kw , without forcing them to place them after the last entry. This often makes sense, for example. grouping things logically or organizing keywords in alphabetical order can help in maintenance and development.

Therefore, there is no need to revert to using def(**kwargs) and lose signature information in your smart editor. Your social contract is to provide certain information, forcing (some of them) to require keywords, the order in which they are presented has become irrelevant.

+6
May 31 '17 at 1:41 pm
source share

Update:

I realized that using **kwargs would not solve the problem. If your programmers change the arguments of the function as they wish, you can, for example, change the function to this:

 def info(foo, **kwargs): 

and the old code will break again (because now every function call should include the first argument).

It really comes down to what Brian says.




(...) people can add parameters between spacing and collapse (...)

In general, when changing functions, new arguments should always end. Otherwise, it breaks the code. It should be obvious.
If someone changes the function so that the code breaks, this change must be rejected.
(As Brian says, this looks like a contract)

(...) sometimes it’s not always clear what needs to be done.

Looking at the signature of the function (i.e. def info(object, spacing=10, collapse=1) ), you should immediately see that each argument that has no default value is required.
For what the argument is needed , it must go into docstring.




Old answer (retained for completeness):

This is probably not a very good solution:

You can define functions this way:

 def info(**kwargs): ''' Some docstring here describing possible and mandatory arguments. ''' spacing = kwargs.get('spacing', 15) obj = kwargs.get('object', None) if not obj: raise ValueError('object is needed') 

kwargs is a dictionary containing any keyword argument. You can check if a required argument is present, and if not, raise an exception.

The disadvantage is that it may not be so obvious which arguments are possible, but with a proper docstring, this should be fine.

Strike>

+2
Jun 03 2018-10-06T00:
source share

You can only declare your functions when receiving **args . This will require keyword arguments, but you will have additional work to make sure that only valid names are passed.

 def foo(**args): print args foo(1,2) # Raises TypeError: foo() takes exactly 0 arguments (2 given) foo(hello = 1, goodbye = 2) # Works fine. 
+1
Jun 03 2018-10-06T00:
source share

You can use the ** operator:

 def info(**kwargs): 

Therefore, people are forced to use named parameters.

0
Jun 03 '10 at 11:02
source share
 def cheeseshop(kind, *arguments, **keywords): 

in python, if use * args means that you can pass an n-number of arguments for this parameter - for this a list will be created inside the function to access

and if you use ** kw, that is, its keyword arguments, which can be accessed as a dict, you can pass the n-number of kw args, and if you want to restrict this user, enter the sequence and arguments, 't use * and ** - (its pythonic way of providing common solutions for large architectures ...)

if you want to limit your function to default values, you can check inside it

 def info(object, spacing, collapse) spacing = spacing or 10 collapse = collapse or 1 
0
Jun 03 '10 at 11:11
source share

As other answers say, changing function signatures is a bad idea. Either add new parameters to the end, or correct each caller if arguments are inserted.

If you still want to do this, use the function decorator and inspect.getargspec . It will be used something like this:

 @require_named_args def info(object, spacing=10, collapse=1): .... 

The implementation of require_named_args left as an exercise for the reader.

I would not bother. It will be slow every time the function is called, and you will get better results from writing the code more carefully.

0
Jun 03 '10 at 12:50
source share

Python3 keyword-only arguments ( * ) can be modeled in python2.x with **kwargs

Consider the following python3 code:

 def f(pos_arg, *, no_default, has_default='default'): print(pos_arg, no_default, has_default) 

and his behavior:

 >>> f(1, 2, 3) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: f() takes 1 positional argument but 3 were given >>> f(1, no_default='hi') 1 hi default >>> f(1, no_default='hi', has_default='hello') 1 hi hello >>> f(1) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: f() missing 1 required keyword-only argument: 'no_default' >>> f(1, no_default=1, wat='wat') Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: f() got an unexpected keyword argument 'wat' 

This can be modeled using the following, mind you, I took the liberty of switching TypeError to KeyError in the case of “named argument required”, it would not be too much work to make this type the same exception, but

 def f(pos_arg, **kwargs): no_default = kwargs.pop('no_default') has_default = kwargs.pop('has_default', 'default') if kwargs: raise TypeError('unexpected keyword argument(s) {}'.format(', '.join(sorted(kwargs)))) print(pos_arg, no_default, has_default) 

And the behavior:

 >>> f(1, 2, 3) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: f() takes exactly 1 argument (3 given) >>> f(1, no_default='hi') (1, 'hi', 'default') >>> f(1, no_default='hi', has_default='hello') (1, 'hi', 'hello') >>> f(1) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in f KeyError: 'no_default' >>> f(1, no_default=1, wat='wat') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 6, in f TypeError: unexpected keyword argument(s) wat 

The recipe works just as well in Python3.x, but it should be avoided if you are only Python3.x

0
May 08 '19 at 16:17
source share

I do not understand why the programmer will add the parameter between the other two in the first place.

If you want function parameters to be used with names (for example, info(spacing=15, object=odbchelper) ), then it doesn't matter in which order they are defined, so you can also add new parameters at the end.

If you want the order to matter, you cannot expect anything to work if you change it.

-2
Jun 03 2018-10-06T00:
source share



All Articles