6. Functions

6.1. Script [fonc_01]: variable scope
The script [fonc_01] shows examples of variable scope between functions:
# variable scope
def f1():
# Avoid using global variables
# global variable i
global i
i += 1
# local variable j
j = 10
print(f"f1[i,j]=[{i},{j}]")
def f2():
# Avoid using global variables
# global variable i
global i
i += 1
# local variable j
j = 20
print(f"f2[i,j]=[{i},{j}]")
def f3():
# local variable i
i = 1
# local variable j
j = 30
print(f"f3[i,j]=[{i},{j}]")
# main program
i = 0
j = 0
# these two variables will be known to a function f
# only if the function explicitly declares with the `global` statement that it wants to use them
# or if the function only reads the global variable
f1()
f2()
f3()
# j hasn't changed, but i has
print(f"[i,j]=[{i},{j}]")
Results
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/functions/fonc_01.py
f1[i,j] = [1,10]
f2[i,j] = [2, 20]
f3[i,j] = [1, 30]
[i,j] = [2,0]
Process finished with exit code 0
Notes:
- The script demonstrates the use of the variable i, declared as global in functions f1 and f2. In this case, the main program and functions f1 and f2 share the same variable i.
6.2. Script [fonc_02]: variable scope
The script [fonc_03] builds on the script [fonc_02] and shows how to avoid using global variables:
# variable scope
def f1(i):
# local variable i
i += 1
# local variable j
j = 10
print(f"f1[i,j]=[{i},{j}]")
# return the modified value
return i
def f2(i):
# local variable i
i += 1
# local variable j
j = 20
print(f"f2[i,j]=[{i},{j}]")
# return the modified value
return i
def f3():
# local variable i
i = 1
# local variable j
j = 30
print(f"f3[i,j]=[{i},{j}]")
# main program
i = 0
j = 0
# these two variables will only be known to a function f
# only if the function explicitly declares with the `global` statement that it wants to use them
# or if the function only reads the global variable
i = f1(i)
i = f2(i)
f3()
# j hasn't changed, but i has changed
print(f"[i,j]=[{i},{j}]")
Comments:
- lines 2, 12: instead of being declared global, the variable [i] is passed as a parameter to the functions f1 and f2;
- lines 9, 19: the functions f1 and f2 return the modified variable [i] to the main program. The main program retrieves it on lines 36 and 37;
Results
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/fonctions/fonc_02.py
f1[i,j] = [1, 10]
f2[i,j] = [2, 20]
f3[i,j] = [1, 30]
[i,j] = [2,0]
Process finished with exit code 0
6.3. Script [fonc_03]: scope of variables
The script [fonc_03] demonstrates a peculiarity of variables used both within a function and in the code calling it, depending on whether the variable is used for reading only within the function or not.
def f1():
# here the global variable i is known
print(f"[f1] i={i}")
# here the global variable j is known
print(f"[f1] j={j}")
def f2():
# Here, the global variable i is not known
# because the function f2 defines a local variable with the same name
# so it takes precedence
try:
# attempt to display the local variable i defined further down
print(f"[f2] i={i}")
# the following statement makes i a local variable of the f2 function
i = 7
except BaseException as error:
print(f"[f2] error={error}")
def f3():
# Here, the global variable i is not known
# because the f3 function defines a local variable with the same name
# so it takes precedence
# the following statement makes i a local variable
i = 7
# display - here i is known
print(f"[f3] i={i}")
# main -----------
# Global variables for functions
i = 10
j = 20
# call to f1
f1()
print(f"[main] i={i}, j={j}")
# call to f2
f2()
print(f"[main] i={i}")
# call to f3
f3()
print(f"[main] i={i}")
Notes
- line 34: the main code defines a variable [i];
- lines 1–5: the function f1 also uses a variable [i] without assigning a value to it. This is a read of the variable [i]. In this case, the variable [i] used is the one from the calling code, line 34;
- lines 8–18: the function f2 also uses a variable [i] but assigns a value to it on line 16. Assigning a value to the variable [i] in f2 automatically makes [i] a local variable of the function [f2]. This function therefore “hides” the variable [i] from the calling code;
- line 14: the write operation on the local variable [i] will fail because it has no value when line 14 is reached. It obtains its value on line 16. An exception will occur. For this reason, line 14 has been placed within a try/catch block;
- lines 21–29: the f3 function does the same thing as the f2 function but defines its local variable [i] earlier;
Results
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/fonctions/fonc_03.py
[f1] i=10
[f1] j=20
[main] i=10, j=20
[f2] error: local variable 'i' referenced before assignment
[main] i=10
[f3] i=7
[main] i=10
Process finished with exit code 0
6.4. Script [fonc_04]: parameter passing mode
The script is as follows:
# function f1
def f1(a):
a = 2
# function f2
def f2(a, b):
a = 2
b = 3
return a, b
# ------------------------ main
x = 1
f1(x)
print(f"x={x}")
(x, y) = (-1, -1)
(x, y) = f2(x, y)
print(f"x={x}, y={y}")
Results
Notes:
- Everything is an object in Python. Some objects are called "immutable": they cannot be modified. This is the case for numbers, strings, and tuples. When Python objects are passed as arguments to functions, it is their references that are passed, unless these objects are "immutable," in which case it is the value of the object that is passed;
- The functions f1 (line 2) and f2 (line 7) are intended to illustrate the passing of an output parameter. We want the actual parameter of a function to be modified by the function;
- Lines 2–3: The function f1 modifies its formal parameter a. We want to know if the actual parameter will also be modified;
- lines 14–15: the actual parameter is x = 1. Line 2 of the results shows that the actual parameter is not modified. Thus, the actual parameter x and the formal parameter a are two different objects;
- lines 8–10: the function f2 modifies its formal parameters a and b, and returns them as results;
- lines 17–18: the actual parameters (x, y) are passed to f2, and the result of f2 is assigned to (x, y). Line 3 of the results shows that the actual parameters (x, y) have been modified.
We conclude that when "immutable" objects are output parameters, they must be part of the results returned by the function.
6.5. Script [fonc_05]: Order of functions in a script
The script [fonc_05] shows that a function cannot be called if it has not been encountered previously in the code:
# ------------------------ main
print(f2(100, 200))
# function f1
def f1(a):
return a + 10
# function f2
def f2(a, b):
return f1(a + b)
Notes
- Line 2 will cause an error because it uses the function f2, which has not yet been defined in the script;
Results
6.6. Script [fonc_06]: Order of functions in a script
The script [fonc_06] shows that what applies to the calling code does not apply to functions:
# function f2
def f2(a, b):
return f1(a + b)
# function f1
def f1(a):
return a + 10
# ------------------------ main
print(f2(100, 200))
Notes
- line 3: the function [f2] uses the function [f1] defined later in the script. However, this does not cause an error. We can therefore conclude that the order in which functions are defined in a Python script does not matter;
Results
6.7. Script [fonc_07]: Using Modules
The script [fonc_07] demonstrates how to isolate functions in a module.

We isolate reusable functions in a module. Rather than moving them from one script to another:
- we place them in a separate file that we declare in a specific way;
- scripts that need these functions "import" the module that contains them;
The script [fonctions_module_01] is as follows:
# function f2
def f2(a, b):
return f1(a + b)
# function f1
def f1(a):
return a + 10
There are several ways to ensure that the functions in the [fonctions_module_01] script can be referenced by other scripts. These methods differ depending on whether or not the script is run within [PyCharm].
In [PyCharm], imported modules are searched for in specific folders called [Root Sources]. There are two ways to make a folder a [Root Source]:

- in [4], the folder has changed color;
After this operation, the [functions/modules] folder is recognized as a source folder. You can then write in a script:
To import/use the function f2 defined in the [functions_module_01.py] module.
![]() |
Another method is to use the project properties:
- above, the sequence [1-6] allows the [shared] folder to be used as a folder for storing modules to be imported;
For now, we will not declare any folder as [Sources Root] other than the project root:

Once this is done, we can write the following [fonc-07] script:
# using modules
import sys
# ------------------------ main
print(f"Python path={sys.path}")
from functions.shared.functions_module_01 import f2
print(f2(100, 200))
- Line 2: We import the [sys] object so that we can use its [path] attribute on line 5, which provides what is known as the [Python Path]: a list of directories that will be searched for imported modules;
- line 6: we import the f2 function from the [fonctions_module_01] module. To refer to this module, we use the path leading from the project root to the module. With PyCharm, the project root is always included in the folders searched when looking for an imported module in a script. This folder is therefore part of the project’s [Python Path]. This is what line 5 will allow us to verify;
- Line 6: If we were to describe the path from the project root to the [fonctions_module_01] folder, we would write [fonctions/shared/fonctions_module_01]. In a module’s path, the / is replaced by a dot. We therefore write [fonctions.modules.fonctions_module_01];
- After line 6, the function f2 is defined. We use it on line 8;
Results
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/fonctions/fonc_07.py
Python path=['C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\fonctions', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\functions\\shared', 'C:\\myprograms\\Python38\\python38.zip', 'C:\\myprograms\\Python38\\DLLs', 'C:\\myprograms\\Python38\\lib', 'C:\\myprograms\\Python38', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\venv', 'C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\lib\site-packages']
310
Process finished with exit code 0
Above:
- highlighted in green, we can see that the project root is part of the [Python Path];
- highlighted in yellow, we can see that the folder containing the executed script is also part of the [Python Path];
- the other elements of the [Python Path] come directly from the Python installation folder;
What happens when we don’t use PyCharm to run [fonc-07]?


- in [1], we run the [fonc-07] script. We are located in the [functions] folder;
- In [2], we see that the execution directory is part of the [Python Path]. This is always the case. We can also see that the root directory [C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020] is not part of the [Python Path];
- in [3], the Python interpreter reports that it cannot find the [fonctions] module;
To find the imported module [fonctions.shared.fonctions_module_01], the Python interpreter searches the folders in the [Python Path] for a subfolder named [fonctions]. It cannot find it anywhere. This is because the [functions] subfolder is located under the folder [C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020], which is not part of the [Python Path].
The [fonc-08] script provides a possible solution to this problem.
6.8. Script [fonc_08]: Adding Folders to the [Python Path]
It is possible to modify the [Python Path] programmatically, as shown in the [fonc-08] script:
# using modules
import os
import sys
# script directory
script_dir = os.path.dirname(os.path.abspath(__file__))
# Python path before modification
print(f"Python path before={sys.path}")
# Add the [shared] directory to the Python Path
sys.path.append(f"{script_dir}/shared")
# Python Path after modification
print(f"Python path after={sys.path}")
# import f2
from functions_module_01 import f2
# ------------------------ main
print(f2(100, 200))
Notes
- line 4: the special variable [__file__] is the name of the script being executed. Depending on the execution context, this name can be absolute (PyCharm) or relative (console). The [os.path.abspath] function returns the absolute path of the file whose name is passed to it. The [os.path.dirname] function returns the absolute path of the directory containing the file whose name is passed to it;
- Line 10: [sys.path] is a list containing the names of the directories to search when a module is looked for. We add the project root defined on line 4 to this list;
- we display the [Python Path] before (line 8) and after (line 12) the modification;
- line 15: we import the [fonctions_module_01] module, which contains the f2 function;
Execution in PyCharm yields the following results:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/fonctions/fonc_08.py
Python path before=['C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\fonctions', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\functions\\shared', 'C:\\myprograms\\Python38\\python38.zip', 'C:\\myprograms\\Python38\\DLLs', 'C:\\myprograms\\Python38\\lib', 'C:\\myprograms\\Python38', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\venv', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\venv\\lib\\site-packages']
Python path after=['C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\fonctions', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\functions\\shared', 'C:\\myprograms\\Python38\\python38.zip', 'C:\\myprograms\\Python38\\DLLs', 'C:\\myprograms\\Python38\\lib', 'C:\\myprograms\\Python38', 'C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv', 'C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\\venv\\lib\\site-packages', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\functions/shared']
310
Process finished with exit code 0
- line 3: we see that the [shared] folder appears twice in the [Python Path]. We can avoid this, but it doesn’t cause any issues here;
- line 4: the f2 function was successfully executed;
Now let’s run [fonc-08] in a terminal:
(venv) C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\fonctions>python fonc_08.py
Python path before=['C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\fonctions', 'C:\\myprograms\\Python38\\python38.zip', 'C:\\myprograms\\Python38\\DLLs', 'C:\\myprograms\\Python38\\lib', 'C:\\myprograms\\Python38', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\venv', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\venv\\lib\\site-packages']
Python path after=['C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\fonctions', 'C:\\myprograms\\Python38\\python38.zip', 'C:\\myprograms\\Python38\\DLLs', 'C:\\myprograms\\Python38\\lib', 'C:\\myprograms\\Python38', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\venv', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\venv\\lib\\site-packages', 'C:\\Data\\st-2020\\dev\\python\\cours-2020\\python3-flask-2020\\functions/shared']
310
- line 2: as before, the [shared] folder is not in the [Python Path];
- line 3: now it is;
- line 4: the function f2 was found;
6.9. Script [fonc_09]: declaring parameter types
The [fonc_09] script shows that you can declare the type of a function’s parameters as well as that of the result. However, this declaration is only useful for documenting the function. The Python interpreter does not check whether the actual function parameters are of the expected type. However, PyCharm flags type inconsistencies between actual and formal parameters. This reason alone makes type declarations indispensable.
The script is as follows:
# a function with parameter type declarations
# this serves only as documentation because the Python interpreter ignores it
def show(param: int) -> int:
print(f"param={param}, type(param)={type(param)}")
return param + 1
# main -------------------------
print(show(4))
show("xyz")
Notes:
- Line 5: We declare that the formal parameter [param] is of type [int] and that the function's return type is also [int];
- line 11: the actual parameter of the function [show] is of the correct type;
- line 12: the actual parameter of the function [show] is not of the correct type;
Results
- line 10: the type of the parameter [param] is [str]. When this message appears, we have already entered the code of the function [show]. The Python interpreter has therefore accepted that the actual parameter of the function [show] is of type [str];
- Line 7 of the code triggers the exception shown in lines 4–10 of the results;
PyCharm nevertheless indicates that there is an issue:

In [1], PyCharm has highlighted the incorrect call.
6.10. Script [fonc_10]: named parameters
To pass parameters to a function, you can use the names of its formal parameters. In this case, you are not required to follow the order of the formal parameters:
# you can refer to the actual parameters by their formal names
def f(x, y):
return x + y
# main
print(f(y=10, x=3))
Notes
- line 2: the function f has two formal parameters, x and y;
- line 7: when calling the function f, you can use the names of the formal parameters. This practice can be useful in at least two cases:
- the function has many parameters, most of which have default values. When calling the function, the technique described above allows you to initialize only those parameters for which you do not want to use the default value;
- if the formal parameters have meaningful names, then using named parameters in the function call improves code readability;
Results
6.11. Script [fonc_11]: recursive function
The script [fonc_11] is an example of a recursive function (which calls itself):
# recursive function
def fact(i: int) -> int:
# factorial(1) is 1
# a recursive function must terminate at some point
if i == 1:
return 1
else:
# factorial(i) = i * factorial(i - 1)
return i * fact(i - 1)
# ---------- main
print(f"fact(8)={fact(8)}")
Comments
- lines 1-9: the factorial function;
- line 9: the [factorial] function calls itself;
- lines 5-6: a recursive function must always stop when a condition is met; otherwise, we have infinite recursion;
Results
6.12. Script [fonc_12]: recursive function
The [fonc_12] function provides further details on how recursion works:
# recursive function
# behavior of parameter j
def fact(i: int, j: int) -> int:
# stopping the recursive function
if i == 1:
print(f"j={j}")
return 1
else:
# manipulate j
j += 1
print(f"before fact j={j}")
# recursion
f = fact(i - 1, j)
print(f"after fact j={j}")
# result
return i * f
# ---------- main
print(f"fact(8)={fact(8, 0)}")
Comments
- line 5: we are still focusing on the factorial function. We add the parameter [j] to it;
- line 12: the variable j is incremented regularly with each factorial calculation. We display its value before (line 12) and after (line 16) the recursion (line 15);
Results
- lines 2–8: we see that the value of [j] increases as long as the recursion continues until it meets the condition where the recursion stops. From that point on, the return from calls to the [fact] function occurs in the reverse order of the calls;
- lines 10–16: these displays reflect the successive returns from the factorial call. The variable [j] reverts to its initial value of 1;
