Main Menu: PYTHON PROGRAMMING
Programming in Blocks: Functions
When building programs in the real-world it is essential we write code that is reusable, eliminating the need to rewrite the same blocks of code over and over again. That is where functions come to the rescue. In this chapter, I will teach you how to write reusable blocks of code that can be accessed multiple times in a program. You'll learn to define your own functions and call your functions in your program. You'll also learn to pass data into functions and return data from the functions you have called. Through these, you'll be introduced to the concepts of namespaces and scopes in Python. You'll also learn how to create iterable objects using generators. And finally, we'll start developing our very own student database application which we will upgrade in each following chapter.
5.1: Defining and Calling a Function
NOTE: Before you start, remember to Ceate a New File in IDLE Editor!

Hello there!

If you've visited this blog for the first time: WELCOME!

However if you've been following this block since before today, I want to thank you for sticking around. We're now in the fifth chapter of this blog, and it has been a real blessing that I am able to share with you what I know about programming with Python.

For the past four chapters, we have written code that is executed sequentially, meaning: it starts at one point, follows a straight path, repeats a few steps as it goes:

                    
Sequence 1: 1 -> 2 -> 3 -> 4 -> 5 -> 6-6-6-6 -> 7 -> 8 - > 9.1-9.2-9.3 -> 10
            

and then ends the program. We have never written code that goes:

                    
Sequence 2: 1 -> 2 -> 3 -> 4 -> 5 -> 6: 30-31-32-33 -> 7 -> 8: 30-31-32-33 -> 9 -> 10
            

and then ends.

Sequence 1 is easy to understand: The program executes statements 1 to 5 in order, then executes a for loop which repeats the same statement four times, go on to execute statements 7 and 8, and then executes an if-elif-else statement 9, and finally executes a statement 10.

Sequence 2 however, is different. It executes statements 1 through 5 as in sequence 1, but then encounters a body of statements written inside statement 6, that do not follow the given sequence (meaning they are written as a separate block 30-31-32-33). After executing the block, the program returns to sequence 2 and continues execution from statement 7 up till 8. At 8, it again executes the same block 30-31-32-33, from where it returns to statement 9 and ends at 10.

This body of statements which can be used at different points in a program, and can break away from a program's sequence is called a function.

Functions form the basis of modular programming, because they help you to break your programs into chunks or blocks that can perform specific functions. These blocks (or modules) can be written once and called to work at any point in a program.

Here's is how a function is used in Python. Open IDLE (Python 3.7), go to File > New File. A new file will open in the IDLE Editor. Let's start writing our code:


# Here is the function definition. The function must always be defined before being called.
# Notice the 'tab' indent.
def functionExample():
    print()
    print("You are now inside the function. This is the second line of the function.")
    print("This is the third line of the function.")
    print("This is the fourth line of the function.")
    print("This is the fifth line of the function.")
    print()

# Our main code sequence begins here:
print("This is the first line of the code sequence.")
functionExample()           # This is a "function call".
print("This is the third line of the code sequence.")
functionExample()           # Calling the same function again.
print("This is the fifth line of the code sequence.")
            

After typing out the code in the IDLE Editor, save the code in D:/Python Programming as myFirstFunction.py. Once the code is saved, press F5 or go to Run > Run Module. You should see the folloing output on your screen:


This is the first line of the code sequence.

You are now inside the function. This is the second line of the function.
This is the third line of the function.
This is the fourth line of the function.
This is the fifth line of the function.
                    
This is the third line of the code sequence.

You are now inside the function. This is the second line of the function.
This is the third line of the function.
This is the fourth line of the function.
This is the fifth line of the function.
                    
This is the fifth line of the code sequence.
>>> 
            

Let's analyze the code with reference to the output. You'll see that the we defined the function before the main code. This is necessary, since a function cannot be called by Python unless it has first been defined. The function definition starts with the def keyword and a function name. The function name must have parentheses ( ) attached to it.

The function definition line is followed by the body of the function. This is a an indented body of code which can contain statements, loops and more functions within it.

The main program code is written outside a function. The main code prints a line and then calls the function functionExample(). The program then enters the function and prints a total of six lines. The first line is blank, followed by found sentences, and a blank line again. It then exits the functions and returns to the line of the main code after which it was called. The program then prints the third line. Again, the same function is called in the fifth line. It re-enters the funtion, and executes all the code inside the function. Once again, the program returns to the main code, prints the fifth line of the main code and the program finishes executing.

Did you notice that the sequence of execution of the above program was very similar to sequence 2 shown before?

You should have realized by now that this is not the first time you have used functions. We have been using Python's built-in functions since the first chapter. These are functions that have been defined as part of the Python interpreter you are using. Here are some examples:


C:\Users\Kevin>python37
Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:59:51) [MSC v.1914 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> # Open Python in your command line.
>>> 
>>> # The print() function helps you output values...
>>> print("Hello, World.")
Hello, World.
>>> 
>>> # The str() function converts numbers into strings...
>>> a = 10
>>> type(a)        # The type() function gives you data-type of the variable...
<class 'int'>
>>> a = str(a)
>>> type(a)
<class 'str'>
>>> 
>>> # The input() function allows you to input values into Python...
>>> myName = input("Please enter your name: ")
Please enter your name: Kevin_
>>> print("You have entered: " + myName)
You have entered: Kevin
>>> 
>>> # The quit() function allows you to close the Python terminal
>>> quit()

C:\Users\Kevin>
            

The reason you can use these functions is because they are implicitly loaded by Python when you open Python. They are part of the standard Python library.

In each of the functions we just saw, we entered a value, except for quit(). This is called parameter passing. We will learn about this in the next section.
5.2: Passing Data into a Function
I'm sure you now have a basic idea of what functions are. In the previous section, you learnt about the most basic form a function. In most cases, you will define and call functions to which you will pass data from your main code. For example, when calling the str() function, you passed a variable between parenthesis ( ), e.g.: str(10). This data (variables or data-structures) that you pass into a function are called arguments. By passing arguments, you can send data from your main code to your functions in order to carry out operations on the data. Let's take very simple example. You will need to create a new file in IDLE Editor:


# Notice the function definition. It is different from before.                
def functionWithParameters(parameter001, parameter002, parameter003):
    print("Argument 001: " + str(paramter001))                
    print("Argument 002: " + str(paramter002))
    print("Argument 003: " + str(paramter003))
    print()

# The main program code begins here.
functionWithParameters("Kevin", "Anthony", "Sequeira")
functionWithParameters(10, 20, 30)
functionWithParameters(["Avengers Endgame", "Dawn of Justice"], "The Winter Soldier", 4.7)
            

After typing out the code in the IDLE Editor, save the code in D:/Python Programming as functionsWithParameters.py. Once the code is saved, press F5 or go to Run > Run Module. You should see the folloing output on your screen:


Argument 001: Kevin
Argument 002: Anthony
Argument 003: Sequeira
                    
Argument 001: 10
Argument 002: 20
Argument 003: 30
                    
Argument 001: ['Avengers Endgame', 'Dawn of Justice']
Argument 002: The Winter Soldier
Argument 003: 4.7
>>>               
            

After an argument has been passed into a function, it is called a parameter. In order to enable argument passing, these parameters must be defined inside the parenthesis ( ). They take on values of arguments in the order that arguments are passed during function call. Also, the number of arguments passed during function call must match the number of parameters defined during function definition.
5.3: Understanding Namespaces and Scopes
As we go ahead, I'd like us to think about what we've explored in the previous chapters. More specifically, I'd like to think about the manner in which we developed programs.

In Chapter 1, you were introduced to the different user interfaces through which you could develop your Python programs. You learned to use your machine's command line interface (CLI), the IDLE Editor Notepad, and the IDLE Terminal. In Chapter 2, you learned about data-types and operations, primarily working on the CLI. Similarly, in Chapter 3, you used the IDLE Editor to learn about special statements and looping structures in Python. Your world became cool. Chapter 4, introduced you to the world of data-structures and comprehensions. Your world became cooler. Once again, you used the CLI to type out and execute your code.

Through all of this, we noticed something important: an object, be it a single value variable or a data-structure, can only be initialized once and must have a unique name. For example, consider the following code:


>>> a = ["Kevin", "Sequeira"]
>>> print("Value of 'a':", a)
["Kevin", "Sequeira"]
>>> id(a)
1536067723848
>>> 
>>> a = "Kevin Sequeira"
>>> print("Value of 'a':", a)
"Kevin Sequeira"
>>> id(a)
1536069566832
>>> 
            

From the above code, we see that when a variable is reinitialized the memory location that it points to changes and the old value is lost. We saw this in Chapter 1.

Now, take another example. Open IDLE (Python 3.7), go to File > New File. A new file will open in the IDLE Editor. Let's start writing our code:


# Define a function
def function001():
    a = "Kevin Sequeira"
    print("\n" + "function001() starts here")
    print("Value of 'a' initialized inside 'function001()':", a)
    print("Memory ID for a initialized inside 'function001()':", id(a))
    print("function001() ends here" + "\n")
                
# Main program starts here
a = ["Kevin", "Sequeira"]
print("Value of 'a' initialized in the main program:", a)
print("Memory ID for a initialized in the main program:", id(a))
function001()
print("Value of 'a' initialized in the main program:", a)
print("Memory ID for a initialized in the main program:", id(a))        
            

After typing out the code in the IDLE Editor, save the code in D:/Python Programming as namespacesAndScopes.py. Once the code is saved, press F5 or go to Run > Run Module. You should see the folloing output on your screen:


Value of 'a' initialized in the main program: ['Kevin', 'Sequeira']
Memory ID for a initialized in the main program: 1734324046152
                
function001() starts here
Value of 'a' initialized inside 'function001()': Kevin Sequeira
Memory ID for a initialized inside 'function001()': 1734328367536
function001() ends here
                
Value of 'a' initialized in the main program: ['Kevin', 'Sequeira']
Memory ID for a initialized in the main program: 1734324046152 
>>>              
            


When the program is run, it first defines the funtion 'function001( )'. After that program control moves to the line below the comment # Main program starts here. The program then defines a variable 'a' with a list of strings, and then prints the value and the memory location of the object stored in the variable.

After that, the function calls the function function001( ) and enters the function body. Here's it defines a new variable 'a', and then prints the value and the memory location of the variable. Notice that the the value and the memory location of the variable 'a' are now different. The program then exits the function function001( ) and returns to the "main program".

After the program executes the function block, the program returns to the "main code" and prints the value and the memory location of the variable 'a'. You'll see that the value and memory location of 'a' return to what they were initially.

So, what happened here? The program clearly seemed to switch between two different variables with the same name. We can say that the object 'a' recognized by the program outside the function function001( ) is different from the object recognized inside the function, yet, they share the same name.

Let's modify the code so it looks like this:


# Define a function
def function001():
    a = "Kevin Sequeira"
    print("\n" + "function001() starts here")
    print("Value of 'a' initialized inside 'function001()':", a)
    print("Memory ID for a initialized inside 'function001()':", id(a))
    print("function001() ends here" + "\n")
                
# Main program starts here
function001()
print("Value of 'a' initialized inside 'function001()':", a)
print("Memory ID for a initialized inside 'function001()':", id(a))                  
            

Save the code and hit F5 or go to Run > Run Module. This is the output you would get:


function001() starts here
Value of 'a' initialized inside 'function001()': Kevin Sequeira
Memory ID for a initialized inside 'function001()': 2518352410864
function001() ends here
                
Traceback (most recent call last):
    File "D:\Python Programming\namespacesAndScope.py", line 14, in 
        print("Value of 'a' initialized in the main program:", a)
NameError: name 'a' is not defined
>>>                  
            

As before, the program first defines the function function001( ). It then calls the function function001( ), and program control enters the function body. Here, it defines a variable 'a', and then prints the value and memory location of the variable.

After that, the program control exits the function block of code and returns to the "main code". Once again, the program attempts to print the value and memory location for the variable 'a' initialized inside the function function001( ), but throws an error. This is because the variable initialized inside a function is not recognized outside the the function.

Let's keep this is mind, and modify the code once again:


# Define a function
def function001():
    print("\n" + "function001() starts here")
    print("Value of 'a' initialized in the main program:", a)
    print("Memory ID for a initialized in the main program:", id(a))
    print("function001() ends here" + "\n")
                
# Main program starts here
a = ["Kevin", "Sequeira"]
print("Value of 'a' initialized in the main program:", a)
print("Memory ID for a initialized in the main program:", id(a))
function001()
            

Save the code and hit F5 or go to Run > Run Module. This is the output you would get:


Value of 'a' initialized in the main program: ['Kevin', 'Sequeira']
Memory ID for a initialized in the main program: 1939041077704

function001() starts here
Value of 'a' initialized inside 'function001()': ['Kevin', 'Sequeira']
Memory ID for a initialized inside 'function001()': 1939041077704
function001() ends here

>>> 
            

Once again, the program defines the function function001( ). It then intializes a variable 'a' and prints the value and memory location for the variable. After that, the program calls the function function001( ).

Once the program enters the function body, it attempts to print the value and memory location of the variable 'a' initialized in the "main code" outside the function. The program does this successfully and does not throw an error. This indicates that the variable initialized in a body of code can be recognized inside a function as long as the function is called, or is contained inside the body of code where the variable was initialized. Here, the variable was initialized in the "main code", and the function was also called in the "main code". Thus the value and memory location of 'a' is recognized inside the function function001( ).

From the three editions of the program namespacesAndScope.py, we made the following observations

  1. A variable initialized inside a block of code - in this case a function - cannot be accessed outside that block of code.

  2. A variable initialized outside a block of code - in this case a function - can be accessed inside the block of code as long as the block of code is called in the same code body where the variable was initialized and there is no variable of the same name initialized inside the block of code.

  3. If two variables are initialized with the same name, such that one of them is initialized inside a block of code - in this case a function - and the other is initialized in a code body where that block of code was called - in this case the "main code" - then the variable initialized inside the block of code will have precedence when referred to inside the block of code. In the same manner, the variable initialized outside the block of code has precedence over the variable initialized inside the block of code when referred to outside the block of code.

These three observations bring us to the concepts of namespaces and scopes in Python. A lot of people tend to get confused between the two and use to two interchangably, and I'll do my best to try to put it in the simplest manner possible.

Scope describes all the areas of code where a variable, or rather an object, can be accessed.

A Namespace describes the list of object names that are initialized within a section of code..

These two concepts together decide how variables should be managed when multiple variables are initialized with the same name but in different areas of code. Take the example below. You can create a new IDLE file, or overwrite the code in namespacesAndScope.py.


# Define a function "function001()"
def function001():
    print("function001() starts here.")
    a = "Kevin Sequeira"
    print("Value of 'a' defined inside function001():", a)
    print("Memory ID of 'a' defined inside function001():", id(a))
    print("function001() ends here." + "\n")

# Define a function "function002()"
def function002():
    print("function002() starts here.")
    print("Value of 'a' available inside function002():", a)
    print("Memory ID of 'a' available inside function002():", id(a))
    print("function002() ends here." + "\n")

# Define a function "function003()"
def function003():
    print("function003() starts here.")
    print("function002() is called inside function003()")
    function002()
    print("function003() ends here." + "\n")

# Main program starts here"
a = ["Kevin", "Sequeira"]
print("Value of 'a' initialized in the main program:", a)
print("Memory ID for a initialized in the main program:", id(a), "\n")
print("function001() is called from the main program.")
function001()
print("function002() is called from the main program.")
function002()
print("function003() is called from the main program.")
function003()
            

Save the code and hit F5 or go to Run > Run Module. This is the output you should get:


Value of 'a' initialized in the main program: ['Kevin', 'Sequeira']
Memory ID for a initialized in the main program: 1942245329032 

function001() is called from the main program.
function001() starts here.
Value of 'a' defined inside function001(): Kevin Sequeira
Memory ID of 'a' defined inside function001(): 1942249683184
function001() ends here.

function002() is called from the main program.
function002() starts here.
Value of 'a' available inside function002(): ['Kevin', 'Sequeira']
Memory ID of 'a' available inside function002(): 1942245329032
function002() ends here.

function003() is called from the main program.
function003() starts here.
function002() is called inside function003()
function002() starts here.
Value of 'a' available inside function002(): ['Kevin', 'Sequeira']
Memory ID of 'a' available inside function002(): 1942245329032
function002() ends here.

function003() ends here.
>>>  
            

Let's have a look at the output. The program follows a similar flow as the previous example, only this time it starts with initializing three functions function001( ), function002( ) and function003( ). The program then initializes a variable 'a' and prints the value and memory location of the variable.

The program then calls the function function001( ). The program control enters the function block of code and initializes a variable 'a'. The program then prints the value and memory location of the variable. Note these differ from the value and memory location of the variable initialized outside the function. This is because the variable 'a' initialized inside the function has precedence over the variable 'a' defined outside the function.

The program control then exits function001( ), returns to the "main program" and calls function002( ). Once inside the fuction block of code, the program prints the value and memory location of the variable 'a' available to it. Note these match the value and memory location of the variable 'a' initialized outside function002( ). This is because no new variable of the same name was initialized inside function function002( ). The program first looked for 'a' in the namespace of function002( ). When it didn't find any variable of that name, it scanned the namespace of the code body where function002( ) was called. Here it found a variable 'a' and printed the value and memory location for the variable.

The program control then exits function002( ), returns to the "main program" and calls function003( ). Once inside the function block of code, the program calls the function function002( ). After it enters function002( ) from function003( ), the program prints the value and memory location of 'a' that is available to it. Note these match the value and memory location of the variable 'a' initialized in the "main code". The program first looked for 'a' in the namespace of function002( ). When it didn't find any variable of that name, it scanned the namespace of function003( ) from where function002( ) was called. When it didn't find the variable there too, it scanned the namespace of the code body where function003( ) was called. Here it found a variable 'a' and printed the value and memory location for the variable.

What does this tell us about scope and namespace? From the above example, we see that the scope of the variable 'a' initialized in the "main code" is the "main code" itself, and any blocks of code - in this case functions - called inside the "main code" This scope also extends to functions called inside a function - in this case function002( ) called inside function003( ). The namespace of this variable is the list of variables initialized in the "main code".

Similarly, the scope of the variable 'a' initialized inside function001( ) is the function block of code and any functions called inside that block of code. The namespace of this variable is the list of variables initialized inside function001( ).
5.4: Returning Values from a Function
So far, we've learnt to:

  1. Defining a function
  2. Passing arguments to a function
  3. Namespaces and scopes

All's well and good. However, namespaces and scopes raise an important question: "If variables initialized inside a function cannot be accessed outside it, how do you make use of the values of these variables outside the function?" Please read the question well...

Python makes this possible with the help of the return keyword. Consider the following example. Copy the code given below into a new IDLE Editor File.


# Define a function "functionWithReturn()" 
def functionWithReturn():
    print("functionWithReturn() starts here.")
    variable001 = "Kevin Sequeira"
    print("functionWithReturn() ends here.")
    return variable001

# Define a function "functionWithoutReturn()" 
def functionWithoutReturn():
    print("functionWithoutReturn() starts here.")
    variable002 = "Kevin Sequeira"
    print("functionWithoutReturn() ends here.")

# "Main Code" execution starts from here
print("Value returned from 'functionWithReturn()':", functionWithReturn(), "\n")
print("Value returned from 'functionWithoutReturn()':", functionWithoutReturn(), "\n")
            

After typing out the code in the IDLE Editor, save the code in D:/Python Programming as returningValues.py. Once the code is saved, press F5 or go to Run > Run Module. You should see the folloing output on your screen:


functionWithReturn() starts here.
functionWithReturn() ends here.
Value returned from 'functionWithReturn()': Annet Sequeira 

functionWithoutReturn() starts here.
functionWithoutReturn() ends here.
Value returned from 'functionWithoutReturn()': None 

>>>  
            

The output is very clear. The program first defines two functions: functionWithReturn( ) and functionWithoutReturn( ). After the functions are defined, the program begins to execute the "Main Code" section of of the program.

The program calls the function functionWithReturn( ) inside a print( ) function. Once inside functionWithReturn( ), the program initializes a variable 'variable001', and then returns the value to the "Main Code" section of the program where functionWithReturn( ) was called. Note that the return statement is written at the very end of the program.

The program prints the returned value and moves to the next line of the "Main Code". This time, the program calls the function functionWithoutReturn( ) inside a print( ) function. Once inside the function block of code, the program initializes a variable 'variable002', and then exits the function without returning any value. Consequently, the "Main Code" prints a "None" value as it doesn't find any value returned by the function.

The line of code which returns a value from a function is called the return statement. You can return more than one value from a function using this statement. We'll see examples of this soon. Also, note that the return statement must be the last statement (or last line of code) inside a function.
5.5: Using Generators to Control Returns from Functions
The return statement that you just learnt about works beautifully when you want return a value or a collection of values after you have completed computing all of them. Take for example the following function that creates a list of random numbers and returns the list at the end of the function. (You don't need to run the code.)


# Import the function "randint()" from the Python Standard Library's "random" module
from random import randint 

# Define a function "randomNumberList()"
def randomNumberList():
    count = int(input("Please enter how many random numbers you'd like to generate: "))
    numberList = []
    for counter in range(0, count):
        numberList.append(randint(1, 100))
    return numberList

# "Main Code" starts here
print("List of Random Numbers:", randomNumberList())
            

The output of this code would simply be:


Please enter how many random numbers you'd like to generate: 10
List of Random Numbers: [65, 77, 46, 8, 78, 83, 4, 10, 18, 91]
>>>                 
            

Seems pretty straigtforward. We defined a function that returns a list of random numbers when called. But let's say, you now wanted to create a list containing numbers 1 to 5, but only want to return one number at a time. You can do this by defining a generator instead of a function. Have a look at the code below. Open IDLE (Python 3.7), go to File > New File. A new file will open in the IDLE Editor. Let's start writing our code:


# Define a generator "yieldCounter()"
def yieldCounter():
    for count in range(1, 6):
        yield count

# "Main Code" starts here
counter = yieldCounter()
print("Value yielded on first call:", next(counter))
print("Value yielded on second call:", next(counter))
print("Value yielded on third call:", next(counter))
print("Value yielded on fourth call:", next(counter))
print("Value yielded on fifth call:", next(counter))
print("Value yielded on sixth call:", next(counter))
            

You can save this program in D:/Python Programming as generatorsInPython.py. Here is the output:


Value yielded on first call: 1
Value yielded on second call: 2
Value yielded on third call: 3
Value yielded on fourth call: 4
Value yielded on fifth call: 5
Traceback (most recent call last):
    File "D:/Python Programming/studentDatabaseUsingFunctions.py", line 14, in 
        print("Value yielded on sixth call:", next(counter))
StopIteration
>>>                 
            

The program is very simple to follow. As usual, the program first defines the generator yieldCounter( ), and then goes on to execute the code written under the comment # "Main Code" starts here.

Note that the generator is defined just like a function, with one small difference. Instead of using the keyword return, we used the keyword yield. Also, note that the yield statement is written inside a for loop.

In the "Main Code" section of the program, we first assign the generator yieldCounter( ) to a variable. The program then uses the next( ) function inside a print( ) function to access the variable 'counter'. This calls the generator yieldCounter( ) which we assigned to the variable 'counter'.

The program enters the generator block of code and runs a for loop which counts from numbers 1 to 5. Inside the loop it encounters the yield statement and the program exits the generator with the value of the variable 'count' (1) initialized by the for loop.

The program prints the value yielded by the generator and goes on to the next line. Once again, it encounters the next( ) function inside a print( ) function to access the variable 'counter'. This calls the generator yieldCounter( ), and the program enters the generator body of code. In the case of functions, we would expect the for loop to restart and terminate when it encounters the yield statement. However, something different happens. The output yielded by the generator shows that the the for loop did not restart, but continued from where it left off.

From the output, we see that this process continues until the for loop iterator inside yieldCounter( ) gets exhausted and the generator can no longer yield values. So, for the first five calls, the generator returns a value, but for the sixth call, the program throws as error. We'll look at how to work around this problem in future examples.
5.6: An Example: Organizing your code with the help of Functions
Let's say you want to write a code for maintaining a Student Database. You'll need to write code that takes input, saves the data and outputs data as and when requested. Also, the program should be able to update data when requested by the user. Each of these tasks can be broken down into different blocks of code in the form of functions.

Let's start with defining a function that reads data and stores it in a dictionary. Open IDLE (Python 3.7), go to File > New File. A new file will open in the IDLE Editor. Let's start writing our code:


# Create a method that accepts student data
def setStudentData(studentDatabase, rollNo):
    print("Please enter student details...")
    firstName = input("First Name: ")
    lastName = input("Last Name: ")
    age = int(input("Age: "))
    hometown = input("Hometown: ")
    print()
    studentDatabase[rollNo] = {"firstName": firstName, "lastName": lastName, "age": age, "hometown": hometown}
    return studentDatabase

# Create a function that outputs student data
def getStudentData(studentDatabase):
    print("Please enter the student name you want to search...")
    firstName = input("First Name: ")
    lastName = input("Last Name: ")
    searchCount = 0
    print()
    for rollNo in studentDatabase.keys():
        if ((firstName == studentDatabase[rollNo]["firstName"]) & (lastName == studentDatabase[rollNo]["lastName"])):
            print("Roll No.:", rollNo)
            print("Full Name:", firstName, lastName)
            print("Age:", studentDatabase[rollNo]["age"])
            print("Hometown:", studentDatabase[rollNo]["hometown"])
            print()
            searchCount = searchCount + 1
    if searchCount == 0:
        print("Please check the first and last name entered.")

# Create a generator that outputs a new roll number for creating a new student record:
def yieldRollNo():
    counter = 0
    while True:
        counter = counter + 1
        if counter < 10:
            yield "0" + str(counter)
        else:
            yield str(counter)
        
# Create a function that accepts an action code that the user inputs and returns
# it to the block of code where the function is called:
def getUserChoice():
    choice = input("""Please enter the code for the action you would like to carry out:
    Code :: Action
       1 :: Add a new record
       2 :: View student details
       3 :: Close 
    Enter here: """)
    print()
    return choice
            

Before we proceed, let's review the three functions created in the above code snippet. The function setStudentData( ) accepts two arguments: the data-structure 'studentDatabase', and the variable 'rollNo'. The function accepts user input such as a student's first name, last name, age and hometown. It then stores the data as a new record and returns the database object to the line where the function was called.

The function getStudentData( ) accepts one argument: the data-structure 'studentDatabase'. This function outputs the student details for all occurrences of a student name. The function accepts user inputs for the students first name and last name, and checks for student records that match the name. If a student record matches the name, it is displayed by the program.

The generator yieldRollNo( ) is pretty straightforward. It yields a new roll number for each time it is called.

Let's continue writing our code...


# Create a function that acts as a user interface for carrying out different actions
# NOTE: Define this function below the functions already created
# WHY? Because this function will call the ones already created.
# When running the program, functions must be defined before the block of code
# where they are called
def getUserAction():
    print("Welcome to Student Database.")
    choice = getUserChoice()

    studentDatabase = {}
    rollNoCounter = yieldRollNo()

    while choice not in ["3"]:
        if choice == "1":
            studentDatabase = setStudentData(studentDatabase, next(rollNoCounter))
            choice = getUserChoice()
        elif choice == "2":
            getStudentData(studentDatabase)
            choice = getUserChoice()
        else:
            print("Please check the choice you have entered... \n")
            choice = getUserChoice()
    else:
        print("You have chosen to exit the program. Thank you.")
        from time import sleep
        sleep(10)
            

Let's review the code we just wrote. We created a function 'getUserAction( )'. This function acts as a user-interface that allows the user to carry out certain actions like adding a student record to the database or fetching a student record from the database. In later editions of this program, we will also learn to update student records and delete existing records.

The function 'getUserAction( )' prompts the user to enter specific codes for actions they would to carry out. The function incorporates a while loop which runs as long as the user does not enter the code to exit the program. Inside the while loop, the program checks the code entered by the user with the help of an if-elif statement, and carries out a specific function based on the code entered. If the code doesn't belong to any one of the pre-defined codes, the program prints a warning for the user and prompts them to re-enter a relevant code.

Finally, if the user enters the code for exiting the database, the program prints a message for the user. It then imports the sleep( ) function from the module time. It then uses the sleep( ) function to wait for 10 seconds before closing the program.

NOTE: The function 'getUserAction( )' is written below the functions called inside it. If it written above 'setStudentData( )', 'getStudentData( )', 'yieldRollNo( )' or 'getUserChoice( )', the program will throw an error.

Below 'getUserAction( )' let's write the "main program" section of our code.


# This is the "main program" section of our code
# NOTE: It must be written below all functions defined in this Python file
# WHY? Because all those functions must be defined before they are called
# This part of the code will be the first section to be executed when the
# code is run                
getUserAction()
            

After typing out the code in the IDLE Editor, save the code in D:/Python Programming as studentDatabaseWithFunctions.py. Once the code is saved, press F5 or go to Run > Run Module. You should see the folloing output on your screen:


Welcome to Student Database.
Please enter the code for the action you would like to carry out:
    Code :: Action
       1 :: Add a new record
       2 :: View student details
       3 :: Close 
    Enter here: 1

Please enter student details...
First Name: Kevin
Last Name: Sequeira
Age: 27
Hometown: Mumbai

Please enter the code for the action you would like to carry out:
    Code :: Action
       1 :: Add a new record
       2 :: View student details
       3 :: Close 
    Enter here: 1

Please enter student details...
First Name: Annet
Last Name: D'Souza
Age: 26
Hometown: Mumbai

Please enter the code for the action you would like to carry out:
    Code :: Action
       1 :: Add a new record
       2 :: View student details
       3 :: Close 
    Enter here: 2

Please enter the student name you want to search...
First Name: Annet
Last Name: D'Souza

Roll No.: 02
Full Name: Annet D'Souza
Age: 26
Hometown: Mumbai

Please enter the code for the action you would like to carry out:
    Code :: Action
       1 :: Add a new record
       2 :: View student details
       3 :: Close 
    Enter here: 3

You have chosen to exit the program. Thank you.
>>> 
            

I am confident you are now understand how functions work, and more importantly, you are now confident how to work with functions. In the next chapter, you will learn about classes in Python and be introduced to the world of object oriented programming.

Seeya!