In the previous chapter, we got a comprehensive introduction to Python functions where we covered concepts like lexical scoping, closures, side effects, arguments and much more.
Arguments are an extremely vital concept related to functions, not just in Python, but in all programming languages that provide function out of the box. They are simply values we pass into a function which can then use them to tailor its process.
However, there are numerous ways to provide these values to functions and numerous ways to accept them from the perspective of functions themselves. All this shall be covered in this chapter.
Specifically, we'll see what are arguments and parameters in general; what are positional and keyword arguments in Python; how to provide default values to function parameters; what are variable — argument-length functions and how to create them using the
What are arguments?
A function can take additional data from the user to carry out its respective task in a specific manner.
As the simplest example, consider a function
sum() that aims to return the sum of two numbers
In order to do what it ought to, the function requires two numbers
b and these are passed to it when it's called.
b are formally known as parameters of the function
Parameters are defined in the header of a function's definition, within the pair of parentheses following the name of the function, as follows:
def func_name(parameters): # function body
When we call a function which has parameters defined, we have to specify values for those parameters (in most cases). For example, in the
sum() function above, the moment we call it, we have to provide the two numbers whose sum we wish to know.
These values are provided inside the pair of parentheses in the function's invocation, and are known as arguments to the function.
So, if we call
20 here are the arguments to
People, generally, mix up the terms 'parameters' and 'arguments', using them interchangeably. Strictly speaking, this is incorrect.
A parameter refers to a name used in the pair of parentheses in a function's definition. An argument, on the other hand, refers to the value passed into the parameter.
Consider the following illustration:
Let's define the function
sum() we've been discussing so far to see how all this theory looks and works.
A simple summing function
Restating its purpose, the function
sum() takes in two numbers
b and returns their sum.
The following code defines the function:
def sum(a, b): return a + b
It simply states that there is a function
sum() that takes in two arguments and returns back their sum.
Note that we don't know yet exactly what value do both these parameters
b have; we have just defined what would happen when values are provided — they would be added together and the result returned.
Now let's call this function to sum up a few numbers:
sum(2 ** 2, 10 ** 3)
The values for
b are provided in the same order that they both have in the function's definition.
In the definition, first comes the parameter
a and then
b; likewise the first argument to
sum() would denote the value of
a while the second argument would denote the value of
These types of arguments, that are put in parameters based on their positions in the invocation expression, are the most common, and known as positional arguments.
Remember that there is nothing in the definition of
sums() that specifies that the arguments to
b have to be numbers.
We provide numbers since we know that sums are usually associated with numbers — who would sum dictionaries or tuples, or Booleans anyway?
But we can call
sum() and pass in non-numeric arguments as well.
Consider the snippet below:
sum('Hello ', 'World!')
Here we pass two strings to
sum() and yet get something returned. As we know, what we get here is not the sum of the strings, but rather their concatenation.
The function receives two arguments and returns back the result of performing the
+ operator on them. It doesn't know at all as to what type of arguments they are!
We could also pass two entirely meaningless arguments to the function and still get it executed, but not necessarily to completion:
Here we pass a Boolean and a dictionary to
sum() and sensibly get an error thrown — who told us that we could add Booleans to dictionaries!
This implies a very important thing, that's common to variables in Python as well (in fact, parameters are also variables, so the distinction is generally useless b/w parameters and variables).
And that important thing is that there is no way to fix the type of function parameters in Python. This is the reason why it's referred to as a dynamically-typed language — a variable can hold values of different types.
In some other languages that are based on a statically-typed model — such as C++, Java, etc. — a function parameter defines its type. Passing an argument of some other type results in an error.
What we witnessed in the code above was an instance of positional arguments. Let's review what are positional arguments.
The idea is basic — the first argument is matched up by the first parameter, the second one is matched up by the second parameter, and so forth
Most of the argument that you'll work with in functions in Python will be positional. It's the simplest of all kinds of arguments, and so withnesses the largest amount of usage throughout Python.
Let's consider more examples using positional arguments.
Below we define a function to compute the distance between two pairs of co-ordinates on a Cartesian plane.
In each of the invocation from lines 6 to 9, we pass in positional arguments, to the function
y2as we shall see in the next section can be provided values using keyword arguments as well — another kind of function arguments. It's only if we explicitly specify that they must be positional that doing so would become the cause of an error. We'll see this in the last section.
Keyword arguments, as the name suggests, are specified with a keyword. The keyword is the name of a specific parameter for whom the corresponding value is specified.
name=valuepair are known as keyword arguments.
A keyword argument looks exactly like a variable assignment expression — a name followed by an equals sign followed by the value binded to that name.
Let's consider a quick example. Below we have our same old
sum() function with its two parameters
def sum(a, b): return a + b
We'll call it to add the numbers
20, as before, but this time passing the values to the parameters using keyword arguments.
a = 10 is a keyword argument that assigns the value
10 to the parameter
b = 20 is another keyword argument that assigns the value
20 to the parameter
Note that since keyword arguments directly bind a value to a given parameter, it doesn't matter whether one keyword argument comes before the other one. The order of keyword arguments simply doesn't matter...
This means that the invocation
sum(b = 20, a = 10) is as good as the invocation
sum(a = 10, b = 20).
In this case, it isn't invalid to call
sum() and pass values to the parameters
b via keyword arguments, but in most of Python's built-in functions, it is.
Even we could define our own function parameters in a way that restricts them to only accept positional arguments, or only keyword arguments. We'll see how to do this using the characters
*, in the last section of this chapter.
Often times, when defining a parameter in a function, it's desirable to give it a default value, in case the user omits passing an argument for it.
For example, consider the code below:
def confirm(msg): inp = input(msg) return True if inp == 'y' else False
We have a function
confirm() that shows the user a confirmation prompt, and returns back
True if the user enters
'y' or else
Let's say that most of the times, we just want to show the message
'Confirm?'. Now in order to do so, with this definition in place, we would need to provide the message each time we call
def confirm(msg): inp = input(msg) return True if inp == 'y' else False # some process confirm('Confirm?') # some other process confirm('Confirm?')
If we don't want to display a specific prompt message, the
msg parameter still needs to be provided the same string value, over and over again. This is not ideal!
A better way is to give the
msg parameter a default value.
The general syntax of defining a default value for a parameter is shown below:
def func_name(param_name = default_value): # function's body
The parameter's name is followed by an equals sign which is followed by the default value. See how this resembles variable assignment and keyword argument syntax.
In our case, we need to set the string
'Confirm?' as the default value for the
msg parameter in the function
This is accomplished below:
def confirm(msg='Confirm?'): inp = input(msg) return True if inp == 'y' else False # some process confirm() # some other process confirm()
See how the invocations in 6 and 9 don't provide any argument to the function. This is because, in both of them we desire to make a general prompt message asking the user for his/her confirmation — nothing specific is desired to be asked.
However, if a specific prompt message is desired, then the developer can pass in the respective string as the
msg argument. In this case, the given argument would be used as the value for the
msg parameter, instead of its default value.
confirm('Are you sure you want to exit? ')
If you recall it, you'll see that many built-in functions and methods in Python have default-valued parameters.
Let's consider a few of these built-ins...
print() function takes in a value and prints it to the REPL shell, ending it with a given character, or sequence of characters.
This ending can be provided via the
end keyword argument. Be default, it has the value
'/n' which denotes a newline character. That is, each
print() statement ends with a new line i.e subsequent outputs in the shell each appear on a new line.
As stated, we can change this normal behavior by explicitly providing a value to the
end parameter, by means of a keyword argument.
print('Hello', end=' ') print('World')
endparameter in the
print()function, using a positional argument. This is because Python parses all positional arguments to
print()as part of the output stream.
endcan only be provided a value via a keyword argument.
sorted() function can be used to get back a sorted copy of a given iterable.
The iterable is required and provided to the function as a positional argument, after which we can additionally specify whether or not to reverse sort the iterable, using the
reverse keyword argument.
If omitted, it defaults to
False, which simply means that reverse-sorting (descending order) isn't desired.
sorted([11, 2, 9, 10, -5, -10])
sorted([11, 2, 9, 10, -5, -10], reverse=True)
As another example, we have the
int() function can be used to convert a numeric string into an integer value in Python. The string is provided as a positional argument, while an additional base description is given via the
base specifies the base of the numeric string, defaulting to
10 i.e the base of decimal numbers.
Other possible values are
2 for binary;
8 for octal; and
16 for hexadecimal numbers.
One crucially important thing to understand in regards to default-valued parameters is that the default values are computed only once, and that is when compiling the function.
Knowing this can prevent unexpected errors from happening when you work with such functions.
Let's consider an example.
Given the code below, try to predict its output:
i = 5 def f(x = i): print(x) i = 6 f()
Predict the output of the code above.
Did you choose
Well, if you did, then you guessed the wrong output. The correct answer is
But how does this even make sense — we have a global variable
i, it has the value
5, we change it to
6 and then call the function
f(), without an argument. This means that the parameter
x gets the value
i which we know is currently equal to
5 come from?
Well, the scenario discussed here would've been absolutely correct had Python computed default values at runtime, when executing a function.
However, Python doesn't compute default values at runtime. Instead, this happens at compile time, when the function is compiled.
Going with this theory, let's see how the code above actually gets executed.
- A global variable
iis created and assigned the value
- The function
f()is compiled, whereby the default value of its
xparameter is evaluated. The value is
iwhich resolves down to
xpoints to the value
- The global variable
iis changed to
- Next up,
f()is called without any arguments. Thereby the default value of the parameter
xis used which is
5. Recall that this value had been computed when the function was being compiled, at which point
iwas equal to
This behavior is similar to assigning a variable to another variable — both variables refer to the same value whereby reassigning to one does not obviously reassign the other.
But what if the latest value of
i is desired in the case above?
Well, good question!
i from the parameter's default value to inside the function. Go with some placeholder value for the parameter, then on each call of the function, check if the parameter is equal to the placeholder value. If it is, then assign the value of
i to the parameter.
This is illustrated in the code below:
i = 5 def f(x = None): if x == None: x = i print(x) i = 6 f()
The reason this works is simply because a function's body is always executed at runtime.
In Python, it's possible to provide a variable number of arguments to a function. One way to make a function like this is demonstrated above — set default-valued parameters.
Given these parameters, we can make function calls either by providing the respective argument or not providing it. This clearly shows that it's possible to call the given function with a varying number of arguments.
There is yet another way to call functions with a variable length of arguments, in fact two such ways: one is using the
*args parameter and the other is using the
Let's start discussing on the former.
Suppose we want to create a
sum() function that could take in an arbitrary number of arguments and then return back the sum of all of them.
What we want is something like the following:
sum(10, 5, 6, -1)
sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
This could simply be achieved if we use a parameter in the definition of
sum() of the form
arg is the name of the parameter.
*argsis special kind of a parameter that can accept multiple positional arguments, and then provide them encapsulated in a tuple.
Here's how to use
def func_name(*args): # function body
It can only come after normal parameters of a given function. When it's specified, all the arguments, written after those belonging to normal parameters of the function, are passed to the
*args parameter in the form of a tuple.
Each individual argument, is then obviously, accessible by means of bracket notation on the parameter (we just have to retrieve individual elements from a tuple).
Let's consider a simple example:
def f(*args): print(args)
A very simple function
f() is defined here with a single
*args-type parameter, itself named
*args, whose value is printed.
Now, let's call
f() with a bunch of different arguments and see what do we get in the
f(1, 5, 10)
As is clear, whatever we send into
f(), it's output back to us, but in the form of a tuple.
Shown below is another example:
def f(x, *args): print('x:', x) print('args:', args)
Here the function
f() also has an
*args-type parameter, once again named
args, but this time it comes after a normal parameter i.e
Let's see how
args receives values in this case:
f(1, 5, 10)
args: (5, 10,)
In each invocation of
f(), the first argument acts as a positional argument for the parameter
x. Then the rest of the arguments go in the
*args parameter, in the form of a tuple.
The third statement above throws an error, since no argument is provided for the parameter
Coming back to our
sum() function, here's how we can use an
*args-type parameter to make it sum any number of numbers:
def sum(*nums): total = 0 for num in nums: total += num return total
*nums parameter is of the form
*args. It simply means that all arguments provided to
sum() starting from the first argument would be put in a tuple and that tuple passed to the
In the function, we iterate over all elements of the
nums tuple and add them to the
total local variable, finally returning it.
Now if we call something like
sum(10, 20, 30) here's what we'll get:
sum(10, 20, 30)
The best part is that it is perfectly valid to use other parameters in a function in addition to
Below we define a function
power_sum() that takes in a given power and then sums all the provided numbers raised to that power.
def power_sum(pow = 1, *nums): total = 0 for num in nums: total += num ** pow return total
We'd call this function as follows:
power_sum(2, 3, 4)
So what's happening here?
power_sum(2, 3, 4) means adding the numbers
3 ** 2 and
4 ** 2. The first argument i.e
2, goes inside the
pow parameter, while the rest of them go inside the
In the loop, each number in
nums is raised by
pow and then added to the
total is finally returned by the function.
This is a good example to demonstrate that it's possible to use normal parameters along with
*args. In fact, it's very common to do so along with
It's quite occasional to see functions with only an
*args parameter — processing an arbitrary number of arguments, without other configurative arguments, is very rare.
For example, consider the
print() function. It can accept an arbitrary number of arguments and then print them to the shell one after another.
print(10, 20, 30)
print(True, 'Hello', 10, -1, 36.5)
However, it can also be passed other configurative arguments such as
end etc. to further tailor the output:
print(10, 20, 30, sep=', ')
print(10, 20, 30, end='...')
Things to note when using
There are a couple of things one should know before working with the
*args parameter, that could otherwise lead to unexpected errors and poor function layouts, if not known.
They are detailed as follows:
*args are keyword-only
While defining a function, if a parameter comes after the
*args parameter, it is considered to be keyword only.
*argscan only accept keyword arguments.
This is because any number of positional arguments in the invocation expression would go in the
*args parameter, leaving nothing for the one following
*args. That parameter can only
power_sum() function we created above, could be defined in a better way, using this idea. Let's first see what is the problem with it right now.
When we call
power_sum(2, 3, 4), ideally we should get
9 returned. That's because, on the first sight, the expression looks as if it's summing the numbers
power_sum(2, 3, 4) # should ideally return 9
However, as we know, the first argument is not part of the sum; instead, it represents the power to which each number in the sum is raised.
The thing is that, the way our function has been laid out, it's not really easy to understand what's happening in a given invocation. A reader might not be able to make the right intuition regarding the result of
power_sum(2, 3, 4).
The function works absolutely fine for the computer — it's just for the reader that the function isn't able to convey its meaning in a simple way.
What we could do to
power_sum(), to make it more expressive when called, is to change the position of the
Consider the code below:
def power_sum(*nums, pow=1): total = 0 for num in nums: total += num ** pow return total
pow after the
*nums parameter. This means that
pow can only accept keyword arguments, NOT positional arguments. If we don't specify any keyword argument,
pow would obviously default to
Now if we need to sum the numbers
3 ** 2 and
4 ** 2 (each raised to the power
2), we would call:
power_sum(3, 4, pow=2)
Moreover, our previous expression
power_sum(2, 3, 4) would now return
9. This is because there is no keyword argument specified for
pow, and so it would default to
1 i.e add the numbers as they are:
power_sum(2, 3, 4)
As you would agree,
power_sum() is now much more intuitive. Both the given invocations are a lot more meaningful.
For instance, a reader might be able to reason, just by reading, that
power_sum(3, 4, pow=2) sums the squares of the numbers
4 when he/she sees the output
In contrast, reading the expression
power_sum(2, 3, 4) and then the output
25, a reader might not be able to quickly reason the output, or reason it at all!
Laying out functions is surely an art!
*args parameter is allowed
One more thing — it is invalid to have two
*args parameters in a function. What's the reason?
Think on it for a while — two
*args parameters essentially mean the following: 'the rest of the arguments go in the first
*args-type parameter, and then the rest of the arguments go in the second
Do you think this sounds sensible?
Below shown is an illustration:
def f(*a , *b): pass
The syntax error highlights the asterisk before the parameter
b, trying to tell that it's invalid to have more than one parameter with it i.e it's invalid to have more than one
To boil it down, a function can have at most one
*args parameter. That's it!
Below shown is an example:
def f(x, *args): print(x, args) f(30, 20, 5, x=10)
We might expect that this would work as follows: the keyword argument
x=10 assigns a value to the
x parameter, and so the args
5 go in the
But even in saying this out, we don't make any sense, at all!
Python parses code from left to right, which is a trivial detail. It doesn't see the way we might see code.
30 is matched up with the first parameter in the function's definition which is
x, then the rest of the arguments (before the keyword argument) are put in the
*args parameter. Then the keyword argument
x=10 assigns a value to the parameter
x, which already has a value.
Passing two values to a parameter in Python is invalid; hence this throws an error.
The second kind of parameter that makes a function accept a variable number of arguments is
**kwargsis a special kind of a parameter than can accept multiple keyword arguments, and then provide them encapsulated in a dictionary.
The difference between
**kwargs is that the latter receives the rest of the keyword arguments as a dictionary, whereas the former receives the rest of the positional arguments as a tuple.
This is where
**kwargs gets its name from — 'kwargs' stands for 'keyword arguments'.
Each item in the
kwargs dictionary holds one of the given keyword arguments. The item's key represents the name of the argument, while its value represents the value of the argument.
Here's its general form:
def func_name(**kwargs): # function's body
**kwargs denotes the general form of this type of parameter —
kwargs here is the name of the parameter; it's not necessary to use the name
**kwargs in action.
def f(**items): print(items)
f(x=10, y=20, z=30)
As can be seen above, when no arguments are passed to
items parameter is an empty dictionary.
Things to note when using
*args, there are a couple of things to make sure when working with the
**kwargs parameter. Those are discussed as follows...
**kwargs must be the last parameter
When a function is defined with a
**kwargs parameter, no other parameter shall come after it. The
**kwargs parameter must be the last one in the sequence of parameters.
Can you reason why is this so?
**kwargs parameter digests all keyword arguments specified in a function's invocation expression. This means that following
**kwargs there can be no keyword arguments for other parameters — they will end up in
**kwargs instead of those respective parameters.
**kwargs it could've been possible to allow positional arguments, but this would've come at the cost of ambiguity. As one instance, consider the following code:
def (*nums, **items, x): print(x)
First of all note that this is not valid Python code — it's just written to show you why might've the Python community not allowed having any parameter after
Suppose that you want to omit specifying any value to
**items here. You would write something as follows:
f(10, 20, 30, 40) # 40 is meant for x
See the problem with this. The
*nums parameter before
**items, digests all positional arguments; likewise the argument meant for the parameter
x goes into
nums. This would've caused an error.
**kwargs could've potentially caused confusion, both for the Python interpreter and for the human reader.
By disallowing parameters to come after
**kwargs, Python eliminates any sort of ambiguity that could've otherwise crept into programs. It normalises parameter definitions, and makes them look more sensible and pleasing.
*args has one asterisk in it, while
**kwargs has two of them; so naturally our mind would like to have the latter afterwards (in the function's definition).
Summarising this long discussion in a few words, Python doesn't allow having any argument after
**kwargs for very good reasons!
**kwargs parameter is allowed
*args, a function can have at most only one
The reason is similar to that of allowing only one
Consider the code below:
def f(**a, **b):
As expected, the statement returns a syntax error.
**a— NOT because there is a
**kwargs-type parameter following it.
Recall the first point above — it's invalid to have any parameter after
**kwargs. This is what's happening here. Python realises that a parameter follows the first
**aparameter and likewise throws an error.
This example is meant to demonstrate that when we say no parameter can follow
**kwargs, it includes another
**kwargsparameter as well.
* symbols in parameters
Have you ever seen the documentation of a built-in function when writing it in the REPL shell, or when exploring it in Python's docs online?
Often, you'll see the symbols
* used between parameters.
These symbols can be used while defining a function to distinguish between parameters that could accept only positional arguments only, keyword arguments only, or both.
Consider the snippet below:
def func_name(pos_only, /, pos_or_keyword, *, keyword_only): # function body
It specifies that all parameters before
/ can accept only positional arguments, all parameters after
* can accept only keyword arguments, and everything in between them can accept both.
Let's recreate the
confirm() function, we constructed above, to accept only a positional argument in the
def confirm(msg, /): inp = input(msg) if inp == 'y': return True return False
If we now call
confirm(msg='Exit? '), we would get back an error:
This is because the
/ (forward slash) character clearly implies that
msg can only be provided a value via a positional argument.
It's also possible to make
def confirm(*, msg): inp = input(msg) if inp == 'y': return True return False
Now, if we call
confirm('Exit? '), we would get an error, as
msg is now obliged to be supplied a value via a keyword argument only, with the help of the
* (asterisk) character.
Let's create another function that applies both restrictions on given parameters:
def confirm(msg, /, *, true_input): inp = input(msg) if inp == true_input: return True return False
The previous function
confirm() has been modified. We can additionally specify a value in the
true_input parameter to be used in the check returning
True, instead of the default value
Below a couple of calls of this function are made:
confirm('Exit? ', true_input='1')
As we know, without these symbols, function parameters have no restrictions at all. They can be supplied values via both, positional and keyword arguments.