Function Call
Learning Objectives
At the end of this sub-unit, students should
- appreciate the improved readability by naming a sequence of operations.
- know how to invoke functions.
- know how to accept the return value from functions.
- know how to chain functions.
Naming Operations
Our motivation is still captured by the quote by John Woods.
Quote
"Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live. Code for readability."
John Woods
Let us look back at the partial solution for \(e^x\) and \(\binom{m}{k}\).
1 2 3 4 5 6 7 8 9 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
We abstracted the factorial problem and say that we can solve it as we have a code for it. Unfortunately, to use it, we have to use tricky conventions so that there is no name clashes. Wouldn't it be nice if there is a way to do this more cleanly without all of these conventions? After all, we are humans and we may make mistakes during the renaming and composition. A direct support from the Python language will be beneficial.
Luckily, we are not the first people to want this.
There is indeed a support from Python called function call.
With functions, we can rewrite the two code above as follows assuming that we have a function called factorial.
1 2 3 4 5 6 7 8 | |
1 2 3 4 5 6 | |
Now that is a more readable code. There is another advantage here. We do not have to know how the factorial is computed. The code for that is irrelevant. It can be any of the following.
- It is the code we have written before, but someone is computing it with pen and paper.
- The number
nis emailed to a student and the student spent their free time computing the factorial ofnand email us back the answer. - Someone has drawn the graph for \(\Gamma(x)\) function and they simply draw a straight line from \(x + 1\) and look at the corresponding value on the \(y\)-coordinate.
The main thing is, if we are simply using the function, we do not care how the function is written. We only need to know the function specification and how to use the function. This is called function invocation.
Invoking Functions
We have done this before, but we have a very limited of functions to show the actual behavior and potential complications.
So let us introduce more functions from the mathematic library in the math module.
| Function | Description |
|---|---|
factorial(n) |
Returns the factorial of the integer n |
comb(n, k) |
Returns the value of \(\binom{n}{k}\) |
You may have noticed that the functions introduced above are exactly what we have written before. It would have been easier if we have used them. But it is a good exercise to be able to implement them as you may not always use a language that have those functions implemented. To actually use these functions, you will need to add the following import declaration at the beginning of your code.
1 | |
So let us assume we have them. This additional step should not distract us from what we need to learn here and that is, how do we use these functions.
First, notice the way we specify the function.
In factorial, we specify the name n within parentheses.
This corresponds to the way we represent our own factorial as a grey box, shown below.

The input n is exactly the n inside the parentheses of factorial(n).
But now we do not care about the implementation of factorial.
So we do not care about local variables.
This is good, because we may accidentally use the same variable name but it will not interfere with the working of this function.
Secondly, we do not care what is the variable that stores the resulting output. Because clearly that has been defined locally and we say that we do not care about the internals. So we will instead say that there is going to be a value produced and we will describe what this value is. This actually corresponds to the description of the function in the table. We use the terminology returns here because as you will see later, it will correspond to the keyword we will use.
But the main idea is that we can now change the grey box into a black box. One that we do not care about the internals.

The same specification factorial(n) also describes the way we can invoke the function.
We simply use the name, add parentheses after the name, and provide values within the parentheses.
Three things to note about these values.
- The value need not be a constant value (e.g.,
3), but it can be a variable and/or expressions that is evaluated into a value. - The number of values must match the number of inputs that the function accepts.
- We call these values as arguments.
Function Invocation of Factorial
Correct function invocation with correct number of values.
1 2 3 4 5 6 7 8 | |
Incorrect function invocation with incorrect number of values.
1 2 3 4 | |
It has been a while since we use the IDLE REPL so let us remind you that we see something printed because there is a value produced by the function. This printing is because of P in REPL. If we run the code from the editor, we will not see anything printed. To remember the value produced, we should assign it to a variable. In fact, this is what we did above. The code is reproduced below with the addition of the import statement.
1 2 3 4 5 6 7 8 9 | |
1 2 3 4 5 6 7 | |
What We Did Before
We did something similar to this "passing of values". But we did it more explicitly. We started this in the left triangle problem. In that code, we prepared the argument using the following part of the code.
1 2 3 4 5 | |
Here, m is the counterpart of n in factorial(n) and k is the argument.
Multiple Inputs
In the case of factorial, we only have one input.
So there is no ambiguity to which variables accept which inputs because there is only one possibility.
But what if we have multiple inputs like comb(n, k)?
Let us look at the black box representation of it first.

Unfortunately, we cannot really look at what we did before because the order does not matter. We used a different convention which is based on name as shown below for right triangle problem.
What We Did Before
We did something similar to this "passing of values". But we did it more explicitly. We started this in the right triangle problem. In that code, we prepared the argument using the following part of the code.
1 2 3 4 5 6 | |
So what is the convention for function call? The simplest convention is that we use position instead of name. Before we explain the mechanism, we will first give a name to the variable that will accept the argument. We will call this parameters1.
- Evaluate the 1st argument into a value \(V_1\) and assign \(V_1\) to the 1st parameter.
- Evaluate the 2nd argument into a value \(V_2\) and assign \(V_2\) to the 2nd parameter.
- Evaluate the 3rd argument into a value \(V_3\) and assign \(V_3\) to the 3rd parameter.
- and so on
As we have seen, if the number of arguments is different from the number of parameters, then we get TypeError.
This mechanism of passing argument to parameter is called parameter passing.
There are different conventions for these and the convention used by Python is called call by value.
Function Invocation of Combination
Correct function invocation with correct number of values.
1 2 3 4 5 6 7 8 | |
Incorrect function invocation with incorrect number of values.
1 2 3 4 5 6 | |
We can visualize call by value for comb(5, 3) as follows.

No Argument?
Currently we do not have an example of functions that does not take any arguments.
But it is possible to have functions that do not take in any arguments.
Something we have learnt and close to having no argument is the print function because we can actually invoke it without any argument as follows.
1 2 3 | |
However, print is actually a much more complicated function that that because it can be called with any number of arguments.
This is what we call variadic function and it is beyond the scope of our current discussion.
What you should note is simply that even if there is no argument expected, we still need to put parentheses after the function name.
??? danger "Bad Practice" Due to the complexity of Python, there will be no error if we actually forgot the parentheses. But the reason is because of higher-order function. You will see the following if you forgot the parentheses.
1 2 3 4 | |
Chaining
If we look at the way we can accept the return value of factorial(n) closely, we will realize an interesting fact.
Function call is treated as an expression because in lhs = rhs, we allow rhs to be any expression.
So now, in any places that accept expression, we can use function call.
This means that if we want to get a value that is one more than the factorial of \(n\), we can simply write it as follows.
1 2 3 | |
Logically, if we are given \(n\), and we want to find \((n! + 1)!\)2, then we can write it as a sequence of statements like the following code.
We will also exclude from math import * from our notes from this point onwards and assume that all the necessary modules are imported.
Also, we will exclude print to prepare ourselves for solving problems with functions.
1 2 3 | |
But this is exactly the same as if we are not using the variable fact_n_plus_1 and instead, copying the rhs directly inside.
This is the reverse of our way of making the code more readable.
We are doing this because it reveals interesting property about functions, we can chain them.
1 2 | |
Chaining function calls allows us to write shorter code. But it may be less readable. So while this technique is interesting, use it with care. The main aspect we want to highlight is the order of operation.
Since the last two codes above are equivalent, it means that the order of operation should be the same too. This implies that the argument of a function call will be evaluated before the function call. This is captured by the motto of the expression evaluation.
Leftmost Innermost.
Let us illustrate the motto by evaluating x + factorial(factorial(y + 2) + 3).
We will be highlight the sub-expressions to be evaluated and add comments for clarity.
Our current state will be the following.
x + factorial(factorial(y + 2) + 3)
⇒ x + factorial(factorial(y + 2) + 3) [leftmost]
⇒ 2 + factorial(factorial(y + 2) + 3) [substitute]
⇒ 2 + factorial(factorial(y + 2) + 3) [leftmost]
⇒ 2 + factorial(factorial(y + 2) + 3) [innermost]
⇒ 2 + factorial(factorial(y + 2) + 3) [leftmost]
⇒ 2 + factorial(factorial(y + 2) + 3) [innermost]
⇒ 2 + factorial(factorial(y + 2) + 3) [leftmost]
⇒ 2 + factorial(factorial(3 + 2) + 3) [substitute]
⇒ 2 + factorial(factorial(3 + 2) + 3) [innermost]
⇒ 2 + factorial(factorial(5) + 3) [evaluate]
⇒ 2 + factorial(factorial(5) + 3) [leftmost-innermost]
⇒ 2 + factorial(120 + 3) [evaluate]
⇒ 2 + factorial(120 + 3) [leftmost-innermost]
⇒ 2 + factorial(123) [evaluate]
⇒ 2 + factorial(123) [leftmost-innermost]
At this point, the value is so large that it does not make sense to write them down. But hopefully you get the point. We evaluate the leftmost innermost sub-expressions that are not yet evaluated. In other words, if it is not yet a value, we evaluate them to produce a value.
Try to do the evaluation slowly first. Once you have gained enough practice, you should be able to reasonably evaluate any function call. Let us show some quick evaluation using another example by highlighting only the next evaluated expression directly.
comb(factorial(y), factorial(x))
⇒ comb(factorial(y), factorial(x))
⇒ comb(factorial(3), factorial(x))
⇒ comb(6, factorial(x))
⇒ comb(6, factorial(2))
⇒ comb(6, 2)
⇒ 15
Nothing
There are functions that do not return anything.
Put it another way, it returns nothing.
But what is nothing?
This is philosophical conundrum because as soon as we identify what "nothing" is, it becomes something.
Luckily, we are not philosophers, we are computer scientist.
So we can simply say that we have a value to represent nothing.
This type is called the NoneType and there is only one value of this type called None.
Adapted to Python
"None means "Boring". It means the boring type which contains one thing, also boring. There is nothing interesting to be gained by comparing one element of the boring type with another, because there is nothing to learn about an element of the boring type by giving it any of your attention.
It is very different from the empty type [...]. The empty type is very exciting, because if somebody ever gives you a value belonging to it, you know that you are already dead and in Heaven and that anything you want is yours."
So while we cannot represent true nothingness (i.e., empty), we can represent something quite close to it.
Since there is only one value in NoneType, which is None, we will use these two interchangeably.
The usage of None in Python is actually far from boring.
Because it represents nothing, if a function returns nothing, it actually returns None.
IDLE REPL also behave differently in the presence of None.
In particular, because it represents nothing, the P in REPL prints nothing when presented by the value None.
This is really nothing because nothing is printed.
1 2 | |
That is not a typo.
We can even try assigning None to a variable and try to get the value in IDLE.
It will still print nothing.
1 2 3 | |
If we really want to print None, we need to use print(None).
1 2 | |
You are probably wondering, but what is the return value of the print function?
The code above actually already has the answer.
Let us reason about this slowly before proving that our reasoning is indeed correct.
First, we know that None is not printed by IDLE unless we use print(None).
Second, we know that function returns something.
From here, we can deduce that print is returning something that is not printed by IDLE.
The None above is printed by the function, not from the return value of print.
Therefore, we can conclude that print must return None.
In other words, it returns nothing.
So that is our reasoning.
How do we know we are correct?
That is easy, we can assign the return value of print to a variable and check what is that value.
1 2 3 4 5 | |
Now test your understanding, what will be printed by the following code?
1 | |
1 2 | |