One of the cool features of Python is the concept of positional and keyword arguments. In this article, we will explore these two types of arguments and then discuss the right place to use each one.
Positional arguments
Imagine we have a function sub
like this
def sub(a, b):
return a - b
We can call this function as follows
total = sub(10, 5)
In this example, the first parameter a
is bound to the first value 10
and the second parameter b
is bound to the second value 5
. Therefore, the returned result will be 10 - 5 = 5
Here, the parameters are bound to the values based on their position. Therefore these kinds of arguments are called positional arguments.
Keyword arguments
While positional arguments are very common, Python supports another way of passing arguments called keyword arguments. Take a look at the function call below
total = sub(b=10, a=5)
In this example, we explicitly specify which parameter should be bound to which value. So b
will bind to the value 10
and a
will bind to 5
and the answer will be -5
. This form of argument is called keyword argument. When we use keyword arguments, the position is not relevant since the keyword tells which parameter the value should be bound to.
We can also mix both forms as in this example below
def calc(a, b, c):
return a - b + c
value = calc(10, c=3, b=2) # 11
Here, the value 10
is a positional argument and it gets mapped to a
. The remaining parameters c=3
and b=2
are keyword parameters and mapped to c
and b
respectively.
Note that when mixing forms, all the positional arguments have to come first, followed by the keyword arguments.
When to use each form?
While positional and keyword arguments are easy to understand, what is not so easy is to know when to use each one. Should you call a function with positional arguments? Keyword arguments? A mix?
Here is a simple rule-of-thumb to determine this:
- Core parameters are generally positional. What are core parameters? These are the parameters that are required for the function to run
- Options, flags and configurations are generally keyword. These parameters are not the core parameters for the function, but modify how the function behaves
That sounds a little abstract, so let us look at a concrete example. Below is the function signature for the copyfile
function from the shutil
module in the standard library.
shutil.copyfile(src, dst, follow_symlinks=True)
As the name implies, this function copies a file from one place to another. So the src
and dst
parameters are core for the function.
Additionally, there is a flag follow_symlinks
. This flag is not a core part of the function operation, but it changes up how the function behaves. Therefore this parameter should be a keyword parameter.
Enforcing positional or keyword arguments
In the example above, although we know which parameters should be positional and which should be keyword, there is nothing stopping the caller from mixing the parameters. For example, a user might call the function like this
copyfile('file1.txt', 'file2.txt', True)
Here we are passing the follow_symlink
parameter as a positional argument. This obscures the meaning of the function call. How can we enforce it to be a keyword-only argument?
Python gives us a way to do this.
Here is the full function signature for shutil.copyfile
shutil.copyfile(src, dst, *, follow_symlinks=True)
Notice that *
in the signature. This signifies that all parameters after that symbol should be keyword only. Here is what happens if we try to call this function with only positional arguments
>>> shutil.copyfile('file1.txt', 'file2.txt', True)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: copyfile() takes 2 positional arguments but 3 were given
The error it says that there should only be two positional arguments (src
and dst
) while we passed three. The follow_symlink
parameter can only be a keyword argument.
On the other hand, this function call will work
shutil.copyfile('file1.txt', 'file2.txt', follow_symlinks=True)
In a similar vein, we can also enforce some arguments to be positional only. Here is the function signature for the commonly used len function.
len(obj, /)
Here the /
symbol denotes that all the parameters to the left of it should be positional only. Therefore, this call gives an error
>>> len(obj=[1, 2, 3])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: len() takes no keyword arguments
And both of these symbols can be combined. Here is the function signature of the exec built-in function
exec(object, globals=None, locals=None, /, *, closure=None)
For this function, the parameters object
, globals
and locals
have to be passed as positional only because they appear to the left of the /
. On the other hand, closure
is a keyword only argument because it appears to the right of the *
. If there were any arguments between the two symbols, those arguments could be passed using either style.
An interesting thing to note with the exec
function signature is that globals
and locals
have default values. Therefore, it is not mandatory to pass those options. But if you do pass in values for those parameters, then it has to be positional. Clearly the python core developers think that these two parameters are a core part of the exec
function.
Other considerations
While deciding whether a parameter is core or an option is the main consideration, there are others:
- Keyword arguments are explicit and when reading the function call, we can easily understand which parameters are bound to which values without having to go and look up the function signature
- On the other hand, with positional arguments, if we do not know the function signature, we will have to look it up before we can understand the function call
- Conversely, if we rename any parameter in the function signature, it will break all code that uses that parameter as a keyword parameter. On the other hand, positional arguments are unaffected by renaming parameters in the function signature
Summary
In this article, we took a look at positional and keyword arguments. While the concept is easy to understand, there are subtle nuances when deciding which to use in your code. Apart from supporting both types of arguments, Python also allows us to enforce parameters to be positional-only or keyword-only. Hopefully this article has given a deeper understanding into how to use these two types of arguments.
Did you like this article?
If you liked this article, consider subscribing to this site. Subscribing is free.
Why subscribe? Here are three reasons:
- You will get every new article as an email in your inbox, so you never miss an article
- You will be able to comment on all the posts, ask questions, etc
- Once in a while, I will be posting conference talk slides, longer form articles (such as this one), and other content as subscriber-only