Python Tutorial Episode 3

This tutorial is following book "Smarter Way to learn Python"
chapters 41 until 52

Sheet Written by Ahsan Farooqui, www.blog.ahsanfarooqui.xyz

Download editable notebook from Here

Chapter 41 - Functions

Often times, you have to run a set of code more than once to do a specific function

For example. If you have a set of code that takes input, calculates the sum of the two numbers and prints the sum

Now, if we want to run it more than once at different parts of our program, we can definitely write all these lines again and again. But that will make our code unnecessarily long and thus harder for us to debug in case of any error.

Functions are a way to represent a set of line of codes by one name that you can use later.

So in above example, we can simply create a function called get_sum and write lines of code in there. Now, instead of writing sets of code everywhere, we simply write a small function name.

Syntax:

  • Function is defined as follows:
          def function_name(arguments):
              write commands indented
              this is second command
              return is optional keyword that is used at the end to return results
          And this is the regular code outside function

Function Syntax discussion:

  • The keyword def is used to define a funciton
  • The def keyword is always followed by a function name, which is followed by parenthesis. Notice there is a colon at the end of first line.
  • All commands inside function must be indented by 1 tab. Just as we did for For loop.
  • The "return" is a keyword that we'll discuss later.
In [182]:
#Example: 
#Let's take the above example and write code for it first:

# Taking input from user:
number1 = int(input("Please enter first number: "))
number2 = int(input("Please enter second number: "))
print(f"The sum of numbers {number1} and {number2} is {number1+number2} ")
Please enter first number: 12
Please enter second number: 22
The sum of numbers 12 and 22 is 34 
In [183]:
# Lets now covert the above code into a function
def get_sum(): #Notice I have not added anything in parenthesis. We'lll discuss it later
    number1 = int(input("Please enter first number: "))
    number2 = int(input("Please enter second number: "))
    print(f"The sum of numbers {number1} and {number2} is {number1+number2} ")
    
In [184]:
# To call a function, we write its name followed by the parenthesis
get_sum()
Please enter first number: 12
Please enter second number: 22
The sum of numbers 12 and 22 is 34 

Chapter 42 , 43 and 44 - Passing value to Functions

  • Previously, we were taking input from user inside function
  • Lets say that instead of getting input from the user, we wish to find sum of two numbers that can change based on the variable values
  • So now, we wish the pass on the value of number 1 and number 2 when we call the function so that we dont have to put the data explicitly inside.
  • We can do it using passing value in parenthesis that follows function name.

Syntax:

def function_name(value_to_be_passed_1,value_to_be_passed_2):
    sum = value_to_be_passed_1 + value_to_be_passed_2
In [185]:
# Example:
def get_sum(number1,number2):
    print(f"The sum of numbers {number1} and {number2} is {number1+number2} ")
In [186]:
#Now, calling function and passing the values:
get_sum(2,1)
The sum of numbers 2 and 1 is 3 

Sequence of parameters matter!

  • Notice the name that is used inside function i.e. number1 and number2 is identical to the parameters passed.
  • in above function call, get_sum(2,1): 2 corresponds to number1 and 1 corresponds to number2
In [187]:
#Example:

#Lets try to create a funciton that returns the division of two integers

def get_division(number1,number2):
    print(number2/number1)
In [188]:
get_division(2,1)
0.5

Notice that in the function call, number1 is equal to 2 and number1 is equal to 1.

it depends on how we are using these parameters and not sequence. For example, in print function, we use number2/number1.

so even if we inputted 2 first, the value printed is always number2/number1

In [189]:
get_division(1,2)
2.0
In [190]:
# Challenge:
# Create a program, that prints the table of any number inputted by a user. 
# User also tells how many rows to print. 

def create_table(number,rows):
    for i in range(1,rows+1):
        print(f"{number} X {i} = {i*number}")
In [191]:
create_table(4,10)
4 X 1 = 4
4 X 2 = 8
4 X 3 = 12
4 X 4 = 16
4 X 5 = 20
4 X 6 = 24
4 X 7 = 28
4 X 8 = 32
4 X 9 = 36
4 X 10 = 40
In [192]:
#You can pass annything as argument. 
# You can pass an array, a character, a string, a number, anything as argument. 

# Lets try a function that gets marks of 5 exams as a list, name of person, his roll number and his GPA and prints all the information

def print_details(marks,name,roll_num,gpa):
    print(f"Name: {name}\nMarks: {marks}\nRoll Number: {roll_num}\nGPA: {gpa}")
In [193]:
print_details([2,3,4,5,6],"Ahsan",24442,3.11)
Name: Ahsan
Marks: [2, 3, 4, 5, 6]
Roll Number: 24442
GPA: 3.11
In [194]:
#What if you dont pass a value? 
In [195]:
#Here I have removed GPA value and it gave me an error. 
print_details([2,3,4,5,6],"Ahsan",24442)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-195-abe3a7934fe6> in <module>
      1 #Here I have removed GPA value and it gave me an error.
----> 2 print_details([2,3,4,5,6],"Ahsan",24442)

TypeError: print_details() missing 1 required positional argument: 'gpa'
In [196]:
# To overcome this, we can simply add a default value in our function parameters
def print_details(marks,name,roll_num = 12345,gpa = 2.00):
    print(f"Name: {name}\nMarks: {marks}\nRoll Number: {roll_num}\nGPA: {gpa}")
    
print_details(marks=34,roll_num=122,gpa=3.1,name="Ahsan")
print("\n")
print_details(34,122,3.1,"Ahsan")
print("\n")
print_details(34,"ahsan",roll_num=122,gpa=3.1)
Name: Ahsan
Marks: 34
Roll Number: 122
GPA: 3.1


Name: 122
Marks: 34
Roll Number: 3.1
GPA: Ahsan


Name: ahsan
Marks: 34
Roll Number: 122
GPA: 3.1
In [197]:
#Now, instead of giving error, it gives out a default value.
print_details([2,3,4,5,6],"Ahsan")
Name: Ahsan
Marks: [2, 3, 4, 5, 6]
Roll Number: 12345
GPA: 2.0
In [198]:
#you can also specify the name of parameter when calling function
#this is helpful for the fact that 
print_details(name="Ahsan",marks=[2,3,4,5,6])
Name: Ahsan
Marks: [2, 3, 4, 5, 6]
Roll Number: 12345
GPA: 2.0
In [199]:
#TO get any function's parameter names, we can use following command. Replace the print_details with your function name.
print_details.__code__.co_varnames
Out[199]:
('marks', 'name', 'roll_num', 'gpa')

Chapter 45 - Functions: Mixing positional and Keyword Arguments

In [200]:
def print_details(marks,name,roll_num = 12345,gpa = 2.00):
    print(f"Name: {name}\nMarks: {marks}\nRoll Number: {roll_num}\nGPA: {gpa}")
    
print_details(marks=34,roll_num=122,gpa=3.1,name="Ahsan")
print("\n")
print_details(34,122,3.1,"Ahsan")
print("\n")
print_details(34,"ahsan",roll_num=122,gpa=3.1)
Name: Ahsan
Marks: 34
Roll Number: 122
GPA: 3.1


Name: 122
Marks: 34
Roll Number: 3.1
GPA: Ahsan


Name: ahsan
Marks: 34
Roll Number: 122
GPA: 3.1

Types of Arguments

  • Consider above examples and the three print statements. In first statement, we specified inside function exactly which argument takes on which value. This gave us liberty to re-arrange the function arguments. These are called keyword arguments.
  • In the second example, we skipped the keyword and entered the arguments without keywords. Now they became the positional argument. Note that except for marks, which was at its original position, others were passed in wrong arguments and printed wrongly. So for positional arguments, the order is important
  • In third example, we used both positional and keyword arguments. So maybe if you know the first argument is always the marks and you dont know about others, you can specify keyword and place the value in there.

Chapter 46 - Dealing with an unknown number of arguments

  • Sometimes, we dont know what arguments will be or you may consider them optional.
  • These arguments, if passed may give out some value but if not, they may not impact the execution of function.
  • We can add summarize them with passing only one argument name, preceeded by aesteriks

Syntax:

    def function_name(winner, score, **other_information)
In [201]:
#Example
#Lets get a person's profile and print it. Name and age is must while other things he can optionally add. 

def biodata(name,age,**other_information):
    print("Name: ",name)
    print("Age: ",age)
    print(other_information)
    for key,value in other_information.items():
        print(key+" : "+str(value))
    
In [202]:
# You can see how the other_information creates a dictionary itself. 
# You can either use the for loop to display all values one at a time or you can simply print them all by printing the variable 
biodata(name="ahsan",age=12,gender="male",city="Islamabad")
Name:  ahsan
Age:  12
{'gender': 'male', 'city': 'Islamabad'}
gender : male
city : Islamabad

Chapter 47 & 48 - Passing information back from them and storing them in variables.

  • Often times, we use functions to perform some calculation that may take plenty of lines.
  • We previously saw, we can print values after processing inside the function.
  • But what if we want the calculated values back in our main program for further processing?
  • Through functions, we can return one or more values that can then be saved in a variable for further processing.
  • The return values can be integer, string, dictionary, list or even list of list etc.

Syntax:

    def sumoflist(listname):
        sumofnumber = 0
        for i in listname:
            sumofnumber+=i
        return sumofnumber


  • Here, the return is a keyword, that shows which value to return. You can then print or store the returned value as follows:
      listofnumbers = [1,2,3,4,5,6,7,8]
      totalsum = sumofnumbers(listofnumbers)
      print(totalsum)
In [203]:
#Example: Finding sum of lists and returning
def sumoflist(listname):
    sumofnumber = 0
    for i in listname:
        sumofnumber+=i
    return sumofnumber
In [204]:
listname = [1,2,3,4,5,6,7,8,9,10]
totalsum = sumofnumbers(listname)
print(totalsum)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-204-e203b8a5c4ea> in <module>
      1 listname = [1,2,3,4,5,6,7,8,9,10]
----> 2 totalsum = sumofnumbers(listname)
      3 print(totalsum)

NameError: name 'sumofnumbers' is not defined
In [206]:
#Example. write a program that takes cost price as argument, calculates 17% tax on it and returns the final sale price. 
#Then take input from user and return him the value using function call. 
def salepricecalculator(costprice):
    saleprice = costprice + 0.17*costprice
    return saleprice

costprice = float(input("Enter Cost Price"))
print(f"The saleprice of Rs. {costprice} is Rs. {salepricecalculator(costprice)}")

#Notice in this example, we used the salepricecalculator directly in our print function because it is returning a value that we can directly print. 
#Here function call acts just like a variable bearing a value calculated. 
Enter Cost Price30
The saleprice of Rs. 30.0 is Rs. 35.1
In [207]:
#Example, Write a program that gets a number and returns a list of top 10 multiples of that number. 
#Print the list using for loop with number and its multiple value. 

def top10multiples(value):
    multiples = []
    for i in range(1,11):
        multiples.append(value*i)
    return multiples


value = 10
for i in top10multiples(value):
    print(value,i)
10 10
10 20
10 30
10 40
10 50
10 60
10 70
10 80
10 90
10 100
In [208]:
#Example, Write a function that returns a boolean value True if the number is even and false otherwise
#Then use this as a condition in if-else and print in following format: 
# Number --> Even or Number --> Odd

def evenorodd(value):
    if(value%2==0):
        return True
    else:
        return False
    
if(evenorodd(7)):
    print("Even")
else:
    print("Odd")
Odd
In [209]:
#Example, modify above function so thhat now, there is no if-else statement inside the function. 

def evenorodd(value):
    return value%2==0

if(evenorodd(7)):
    print("Even")
else:
    print("Odd")
Odd

Notice now two things:

  1. You can write a command or calculation directly in return without assigning it to a variable
  2. You can pass a function call dirctly into if-else and no need to assign a variable.
  3. </ol

Returning multiple values back and recieving them.

So far you have noticed that a function can return a value, a string, a boolean operator or a list etc...

What if you wish to return more than one lists? or multiple values? We will see an example of it now.

In [210]:
# Get a temperature in degress celcius, convert to fahrenheit and kelvin and return both temperatures. 

def temp_convert(celcius):
    fahrenheit = ((9/5)*celcius) + 32
    kelvin = celcius + 273
    return fahrenheit,kelvin           #Notice here how I returned both values comma-separated.
In [211]:
#Now, at reading, instead of assigning one variable, I can assign 2 variables and the values will be passed accordingly. 
# Like in this example f will have fahrenheit's value and k will have kelvin value
f,k = temp_convert(45)
print(f)
print(k)
113.0
318
In [212]:
#Example: 
# modify upper example so that now, the user is in command of which value he needs. He may pass a value conversion_type: K for kelvin and F for fahrenheit. 
# convrert accordingly. 

def temp_convert(celcius,conversion_type="K"): #By default we set Kelvin. 
    if(conversion_type=="K"):
        return celcius + 273
    elif (conversion_type=="F"):
        return ((9/5)*celcius) + 32
    else:
        print("ERROR! Check conversion Type and retry...")
        
In [213]:
print(temp_convert(32)) #No conversion type passed. 
print(temp_convert(32,"K")) #kelvin
print(temp_convert(32,"F")) #Fahrenheit
temp_convert(32,"P") #Any other value
305
305
89.6
ERROR! Check conversion Type and retry...

49 - Local vs. Global Variables

  • Every variable has a scope
  • A scope is the area from which a variable is accessible.
  • Two types of scopes are defined, one is called global scope, other is called local scope
  • In global scope, the variable is available throughout the file, whereas in local scope, the variable is available within a function or within a certain area etc.
  • Example:

      def some_function():
          this_variable = "Hi"
      print(this_variable) 
  • In the upper example, this_variable is defined inside the function. Hence it will not be accessible from outside the function and once we execute the print statement, it will generate error "NameError: name 'this_variable' is not defined"

  • However, if we define the variable outside function, then we may be able to access it inside the function as well as outside it.

      that_variable = ""
      def some_function():
          that_variable = "Hello"
      print(that_variable)
In [214]:
def some_function1():
    this_variable = "Hi"
print(this_variable)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-214-7bbae4edba80> in <module>
      1 def some_function1():
      2     this_variable = "Hi"
----> 3 print(this_variable)

NameError: name 'this_variable' is not defined
In [215]:
that_variable = "a$5%5%"
def some_function2():
    that_variable = "Hello"
    print("from inside: ", that_variable)

print("from outside: ", that_variable) #It will still give a blank as the function is not called. 
some_function2()
print("from outside after calling function: ", that_variable)
from outside:  a$5%5%
from inside:  Hello
from outside after calling function:  a$5%5%

Lets discuss output of the function above:

  • we defined a variable "that_variable" outside function.
  • then we changed its value inside a function and printed it.
  • outside function, we printed the value thrice. First we printed the value without calling function and as expected, the value was blank.
  • when we called function, the variable was assigned new value INSIDE THE FUNCTION.
  • Afterwards, we callde variable from outside again, it was again printing the initial value.

50 - Functions within functions

  • you can call other functions within one function.
  • Suppose you wish to do multipl things within a function and you wish to break it into pieces. You can have multiple functions that you can call to do any process.
  • Example:
    • Suppose you wish to create a driver's license function that does following
      • takes user's name, age, location,eyesight out of 6 as input
      • checks for user's name in a list and validates name from a list of names
      • checks for the age bracket and responds accordingly. If the age is less than 18, it returns a false, else it gives true.
      • checks location and if it is not pakistan, it rejects application.
      • checks if the eyesight is less than 4, it rejects application.
      • if all checks are fine, the program prints, "Congrats! you have successfully applied for license". Else it prints "You are not eligible due to following reasons: " and then it gives a list of reasons that the user is not complaint with.
In [216]:
# lets create the main function first:

def main(username, userage, userlocation,usereyesight):
    name_valid = validate_name(username) # We took data from user and passed it to validate_name function that responded with a True/False value which we stored in a name_valid function.
    age_valid = validate_age(userage)
    location_valid = validate_location(userlocation)
    eyesight_valid = validate_eyesight(usereyesight)
    print_result([name_valid,age_valid,location_valid,eyesight_valid])
    
# See how easy our main function has become now. in only 5 lines, we are able to call complete function. You can find definitions of each of these functions below. 
In [217]:
# See the main function above before looking these functions. 

def validate_name(username):
    valid_names = ["Ahsan","Ali","Saqib","Dawood","Razzaque","Alina","Ayesha","Hira"]
    if(username in valid_names):
        return True
    else:
        return False
    
def validate_age(userage):
    if(userage>=18):
        return True
    else:
        return False
    
def validate_location(userlocation):
    if((userlocation=="Pakistan")| (userlocation=="PK")):
        return True
    else:
        return False
    
def validate_eyesight(usereyesight):
    if(usereyesight>=4):
        return True
    else:
        return False

def print_result(validate):
    lookups = ["Your Name is Not Valid","Your Age is not Valid","Your Location is not valid","your eyesight is not valid"]
    reasons = [lookups[x[0]] for x in enumerate(validate) if x[1]==False]
    if(len(reasons)>0):
        print("You are not eligible due to following reasons:")
        for x,i in enumerate(reasons):
            print(x+1,"-",i)
    else:
        print("Congrats! you have successfully applied for license")

Let's run the main function with values to see the results.

In [218]:
print("Attempt - 1")
main("Ajaia",18,"Pakistan",5)
print("\nAttempt - 2")
main("Ahsan",17,"India",5)
print("\nAttempt - 3")
main("Ahsan",18,"PK",6)
Attempt - 1
You are not eligible due to following reasons:
1 - Your Name is Not Valid

Attempt - 2
You are not eligible due to following reasons:
1 - Your Age is not Valid
2 - Your Location is not valid

Attempt - 3
Congrats! you have successfully applied for license

51,52 - While Loops / Setting a flag

  • Previously we saw for loops which we have been using alot in our examples. For loop used to have an initial value to start with, and a final value to go to and it used to run for a specific number of times.
  • Simularly, for loops can run through a list, a dictionary, etc.
  • If you wanted to end the loop due to some condition, you used break there.
  • Similarly, to skip an iteration, you used continue function.
  • While loops are a bit different from For loop. They take in a condition and check that condition to meet no matter how many loops it takes. Once the condition is met, the while loop ends.
  • This loop is good for the conditions when you dont know exactly how many times the loop would run.
  • Examples are if you are taking input from user and until user presses a particular button, you keep on taking inputs.
In [219]:
#While Loop Example. 

#Lets create a loop that takes values from user and prints them until the user presses q button
data = ""
while(data!='q'):
    data = input("Enter a value")
    print(data)
Enter a value

Enter a value3
3
Enter a value4
4
Enter a value1
1
Enter a value-1
-1
Enter a valueq
q
In [220]:
#Example
#Using above example, now take numeric data for students. if the user inputs -1, break the loop and then print the average of the numbers already 

marks = [] #initialized a list
value = 0 #initialized a value
while(value!=-1):
    value = int(input("Enter marks or -1 to exit: "))
    if(value!=-1):
        marks.append(value)
average = sum(marks)/len(marks)
print(average)
Enter marks or -1 to exit: 100
Enter marks or -1 to exit: 80
Enter marks or -1 to exit: 12
Enter marks or -1 to exit: 45
Enter marks or -1 to exit: 33
Enter marks or -1 to exit: 55
Enter marks or -1 to exit: 45
Enter marks or -1 to exit: 3
Enter marks or -1 to exit: 4
Enter marks or -1 to exit: 66
Enter marks or -1 to exit: 11
Enter marks or -1 to exit: 44
Enter marks or -1 to exit: 566
Enter marks or -1 to exit: 11
Enter marks or -1 to exit: 5
Enter marks or -1 to exit: 4
Enter marks or -1 to exit: 6
Enter marks or -1 to exit: 18
Enter marks or -1 to exit: 199
Enter marks or -1 to exit: 134
Enter marks or -1 to exit: 13
Enter marks or -1 to exit: 11
Enter marks or -1 to exit: 4
Enter marks or -1 to exit: 5
Enter marks or -1 to exit: 1
Enter marks or -1 to exit: 6
Enter marks or -1 to exit: 1
Enter marks or -1 to exit: 4
Enter marks or -1 to exit: 1
Enter marks or -1 to exit: 99
Enter marks or -1 to exit: -1
52.86666666666667
In [221]:
#Example:
#A user gets $1000 in his account. unless his account is zero or he presses -1, the session terminates and balance is shown. 
#It's obvious that if the balance is less than the inputted amount, the amount must be rejected. 

balance = 1000
amount = 0
while((balance>0) & (amount!=-1)):
    amount = int(input(f"Enter Amount. You currently have ${balance}: "))
    if(amount<=balance):
        if(amount>=0):
            balance = balance-amount
    else:
        print("Inputted amount is more than the balance")    
print(f"Thank you for transactions, Your available balance is: {balance} ")
Enter Amount. You currently have $1000: 50
Enter Amount. You currently have $950: 25
Enter Amount. You currently have $925: 60
Enter Amount. You currently have $865: 250
Enter Amount. You currently have $615: 430
Enter Amount. You currently have $185: 200
Inputted amount is more than the balance
Enter Amount. You currently have $185: 40
Enter Amount. You currently have $145: 60
Enter Amount. You currently have $85: 89
Inputted amount is more than the balance
Enter Amount. You currently have $85: 80
Enter Amount. You currently have $5: 5
Thank you for transactions, Your available balance is: 0 

Advanced topic: Nested While loops

  • Nested While Loops are
In [ ]:
# further, you can check for a condition without user's input and cancel the loop as well. 
# For example, Lets create a pattern from stars that looks like following:


*
* * 
* * * 
* * * * 
* * * 
* * 
*

# So for this we will break our while loop into two parts
# One part will take care of increasing triangle and other will take care of decreasing triangle.
In [236]:
#we create two separate loops for each area. First to tackle the top triangle and second to create bottom triangle.
#In each loop, see how nested while loops are entering aesteriks and next lines
#Dry run these and you'll have better understanding.

row = 0
while(row<=3):
    count = 0
    while(count<=row):
        print("*",end=" ")
        count = count + 1
    print("\n")
    row = row + 1

row = 2
while(row>=0):
    count = 0
    while(count<=row):
        print("*",end=" ")
        count = count + 1
    print("\n")
    row = row - 1
* 

* * 

* * * 

* * * * 

* * * 

* * 

*