Class 16

- Value Returning Functions
- Standard Library Functions
- Creating Random Numbers
- The random Module
- Other random Functions
- Random Number Seeds

- Functions that Return Values
- Returning Values That Are Not Numbers
- Removing Repeated Code
- Advantages of Using Functions
- Stepwise Refinement
- Functions Returning Boolean Values
- Boolean Functions and Data Validation
- Storing Functions in Modules

I have posted homework 7 here.

It is due the Sunday at 11:59 PM.

Let's look at the answers to Quiz 5.

There **will** be a graded quiz next week.

It will cover the material we will discuss today, and the material from Class 13.

- You must have a comment at the top of every script ...
- which describe what the script does
- This script
**must**say something more ... - than "This is the script for homework ..."
- You
**must**use the hash character # ... - to begin a comment
- Every function must have a comment ...
- that comes
**before**the function header - You will lose points if you don't

- Every script and evey function should have a comment ...
- that describe what it does
- Comments should also be used when the code does something usual
- You don't need to write comment for every line of code ...
- or when the code is not doing something weird
- The comments below are unneeded
for cels in range(min, max): #for loop range from 1 to 20 fah = 0 fah = cels * 9/5 + 32 #the formula to convert cels to fah print(cels, "\t",round(fah))

- In the last class we wrote several version of the function cheer
- Each version did more or less the same thing
- But none of them returned a value
- These functions are examples of what our textbook calls void functions
- Most functions do return a value
`input`

returns a string entered by the user`int`

takes a string and return an integer value`range`

returns a list of ordered integers- Functions that return a value are expressions
- Most of the functions you will write in this class will return a value

- All computer languages come with functions that perform useful operations
- Here are some of the Python functions we have used
- int
- float
- input
- range

- All of these are built-in functions
- Built-in functions are actually part of the Python interpreter ...
- so they are always available
- You do not have to do anything special to use them
- But Python also have other useful functions ...
- found in the standard library
- Each standard library is a collection of functions and constants ...
- that deal with a specific topic ...
- and are copied to any machine which installs Python
- Each one of these libraries is contained in a module
- A module is a file containing Python statements ...
- most often functions and variables or constants
- You can use any of the functions and variables stored in a module ...
- by importing them using the
`import`

keyword - Using a function in a standard library is different from using a built-in
- When we use a built-in we simply type the function name
>>> print("Hello") Hello

- But when you need to use something contained in a module ...
- you must first type the name of the module ...
- followed by a dot, . ...
- followed by the name of the function or variable
- This is called dot notation
- Here are some examples
>>> import math >>> math.pi 3.141592653589793 >>> math.sin(math.pi) 1.2246467991473532e-16 >>> math.cos(math.pi) -1.0 >>> math.ceil(4.3) 5 >>> math.floor(4.3) 4 >>> math.floor(-4.3) -5 >>> math.ceil(-4.3) -4

- Random events are events that cannot be predicted
- When writing programs, we often want to use random numbers
- We need them in writing
- Games
- Simulations

- We would like to have functions that can generate random numbers
- But computers are not random
- When we write a function ...
- we determine exactly what the output will be
- Every time we run the program ...
- the same input should give the same output
- The need for random numbers in computing is so great ...
- that people have developed algorithms that produce pseudorandom numbers
- Pseudorandom numbers are number that occurs in a sequence ...
- that eventually repeats ...
- but the interval between repetitions is so long ...
- that they are random enough for writing programs
- The functions that create these pseudorandom sequences are called pseudorandom number generators ...
- or simply random number generators

- One of the modules in the standard library is random
- It contains several several functions that can create pseudorandom numbers
- The function randint takes two integers as arguments ...
- and returns a pseudorandom integer ...
- between the values of its two arguments
- We can call this function several times
- an each time it will return a different number
$ cat random_1.py # demonstrates the use of the randint function of the random module import random for i in range(10): print(random.randint(1,100)) $ python3 random_1.py 89 98 93 73 32 40 63 100 76 80 $ python3 random_1.py 66 49 1 29 63 17 91 3 70 5

- If we called randint long enough ...
- we would eventually repeat the sequence of values
- But we would have to call it a very large number of times

- The random module has other functions to generate pseudorandom numbers
- Each of them has advantages in different situations
- The randrange function also returns an integer ...
- but it gives you more options when specifying the range
- randint takes two arguments ...
- which specify the range of values you want
- The arguments to the randrange function ...
- work just like the arguments to the
`range`

function - When run with one argument ...
- randrange creates number in the range from 0 ...
- to
**one less**than the argument$ cat random_2.py # demonstrates the use of the randrange function of the random module # with one argument import random for i in range(10): print(random.randrange(5)) $ python3 random_2.py 1 3 2 0 1 1 2 2 0 4

- When called with two arguments ...
- the first argument specifies the start of the range ...
- and the second number specifies one more than the end of the range
- When called with 3 arguments ...
- the first two arguments work as before ...
- and the third argument specifies the difference between any two values
- Both randint and randrange return integers
- If you want a decimal number, use random
- It generates a random number between 0.0 and 1.0
$ cat random_5.py # demonstrates the use of the random function of the random module import random for i in range(5): print(random.random()) $ python3 random_5.py 0.3366683809865726 0.6243291094221154 0.47182435685723234 0.3079697111617222 0.5048399470937616

- If you want a random decimal, but want to specify the range of values ...
- use uniform
$ cat random_6.py # demonstrates the use of the uniform function of the random module import random for i in range(5): print(random.uniform(1, 5)) $ python3 random_6.py 3.987702460027857 2.776217942732739 2.890534381287354 1.5377836190792888 3.1461905324732022

- The numbers generated by the functions in the random module ...
- are not truly random
- They are created by specific algorithms ...
- which generate a series of numbers ...
- that only repeat after a
**very**long interval - The algorithm that generates these pseudorandom requires a starting value ...
- called a seed
- If the same value is chosen for the seed each time a program runs ...
- the sequence of numbers created by the random functions ...
- will be the same
- To prevent this from happening the function in random use a trick
- Instead of choosing the same value for the seed ...
- each time they run ...
- they use the system time instead
- But what if we wanted the sequence of numbers to be the same?
- We can do this using the seed function
- The seed function assigns a specific value to the seed ...
- used by all random functions
- If we use the seed function to set the seed value ...
- each time the program is run, we will get the same series of numbers
$ cat random_6.py # demonstrates the use of the seed function in the random module # to generate the same series of numbers each time the program is run import random random.seed(67) for i in range(5): print(random.randint(1, 100)) $ python3 random_6.py 10 15 99 53 60 $ python3 random_6.py 10 15 99 53 60

- Broadly speaking, there are two types of functions
- Functions that return a value
- Functions that do not return a value

- All the functions we have defined so far have not returned a value
- cheer simply prints a string
>>> def cheer(team): ... print("Go " + team + "!") ... >>> cheer("Red Sox") Go Red Sox!

- This function does something ...
- but does not return a value ...
- so we cannot use it in an assignment statement
>>> result = cheer("Pats") Go Pats! >>> result >>>

- Functions that return values can be used in assignment statements
`>>> result = math.sqrt(7) >>> result 2.6457513110645907`

- For a function to give a value back ...
- to the Python statement that called it ...
- it must use the
`return`

keyword - A return statement
is a Python statement that uses the
`return`

keyword ... - and has the format
`return EXPRESSION`

- An expression is anything that the Python interpreter can turn into a value
- A function that returns a value performs a calculation ...
- and then uses a
`return`

statement to send a value ... - back to the function call
- A
`return`

statement does two things- It ends the execution of the function
- It sends a value back to the function call

- Let's create a function that returns the area of a circle
>>> def circle_area(radius): ... area = math.pi * radius ** 2 ... return area ... >>> area = circle_area(5) >>> area 78.53981633974483

- Actually, we can make this function simpler ...
- by removing the assignment statement on the first line ...
- and replacing the variable area in the return statement ...
- with the expression on the right side of the assignment statement
>>> def circle_area(radius): ... return math.pi * radius ** 2 ... >>> area = circle_area(5) >>> area 78.53981633974483

- Now that we have defined this function ...
- we can use it any time we need the area of a circle
>>> area_1 = circle_area(6) >>> area_2 = circle_area(7.5) >>> area_1 113.09733552923255 >>> area_2 176.71458676442586 >>> area_1 > area_2 False

- But we can make the same comparison of two circles ...
**without**using assignment statements- Remember, functions that return a value are expressions
- So instead of calculating area_1 and area_2 ...
- and comparing them with the relational operator >
- We can compare the results of the two function calls directly
>>> circle_area(6) > circle_area(7.5) False

- This code is shorter ...
- and shorter is always better ...
- as long as you can understand it

- Functions are useful whenever a numerical calculation ...
- like finding the area of a circle ...
- has to be made many times
- But functions can return a value of any data type
- Remember the code that turned an integer score ...
- int a grade?
- Turning a score into a grade is something I have to do all the time ...
- so I should take this code ...
- and put it into a function
- Whenever you find yourself writing code ...
- that does that same thing over and over ...
- you should turn that code into a function
- Using functions makes the code shorter ...
- and easier to read
- Here is the function
# returns the letter grade for a numeric score def grade(score): if score > 92: grade = "A" elif score > 89: grade = "A-" elif score > 85: grade = "B+" elif score > 82: grade = "B" elif score > 79: grade = "B-" elif score > 75: grade = "C+" elif score > 72: grade = "C" elif score > 69: grade = "C-" elif score > 65: grade = "D+" elif score > 62: grade = "D" elif score > 59: grade = "D-" else: grade = "F" return grade

- Now that I have this function ...
- I can use it in
`while`

loop ... - to get the grades for many scores
score = int(input("Please enter a score: ")) while score >= 0: print("Score:", score, "\t", grade(score)) score = int(input("Please enter a score: "))

- Notice that I am using the sentinel value of -1 ...
- to tell the program when I have no more scores to enter
- This is a good sentinel value because scores can never be negative
- When we run the script (which you will find here) ...
- we get the following
$ python3 scores_1.py Please enter a score: 89 Score: 89 B+ Please enter a score: 77 Score: 77 C+ Please enter a score: 96 Score: 96 A Please enter a score: -1

- In the code above the same assignment statement
score = int(input("Please enter a score: "))

- appears twice
- Whenever you write the same code more than once ...
- you probably need to turn that code into a function
- This idea is sometimes called the DRY principle
- DRY stands for
**D**on't**R**epeat**Y**ourself - So let me take the code that asks the user for a score ...
- and put it into a function
# prompts the user for a score and returns an integer def get_score(): return int(input("Please enter a score: "))

- The body of the program is now
score = get_score() while score >= 0: print("Score:", score, "\t", grade(score)) score = get_score()

- I have moved the expression that gets the score ...
- from the right side of an assignment statement ...
- to a function definition ...
- and replaced the expression in the assignment statement ...
- with a function call
- You can see the new version of the script here

- What did we gain by moving the expression above to a function?
- We made it easier to read
- I would rather read
score = get_score() while score >= 0: print("Score:", score, "\t", grade(score)) score = get_score()

- than
score = int(input("Please enter a score: ")) while score >= 0: print("Score:", score, "\t", grade(score)) score = int(input("Please enter a score: "))

- But there is another advantage
- The prompt "Please enter a score:" is very polite
- Maybe too polite
- If I were using run the script on many scores ...
- I might want a shorter prompt ...
- like "Score:"
- If I wanted to make that change ...
- and I hadn't created the function
- I would have to make the change in two places
- But if I were in a hurry I might forget ...
- and only change it in one place
- Separating the code into a function ...
- makes it easier to change the code
- which makes the code easier to maintain
- Running the code I get
$ python3 scores_3.py Score: 95 Score: 95 A Score: 72 Score: 72 C- Score: 66 Score: 66 D+ Score: -1

- Looking at the output of the code above ...
- I noticed that the score is printed twice ...
- which looks odd and is unnecessary
- The score is printed by both the call to
`input`

in the function ... - and the
`print`

statement in the while loop - To change things so that the score is only printed once ...
- I only need the the
`print`

statementscore = get_score() while score >= 0: print("Grade:", grade(score)) score = get_score()

- Running the latest version of the script ...
- I get
$ python3 scores_4.py Score: 75 Grade: C Score: 63 Grade: D Score: 80 Grade: B- Score: -1

- I have now created 4 versions of the same script ...
- each one a little bit better than the previous one
- Each time I wrote one version of the script ...
- I saw something I could improve
- After I got that improvement working ...
- I saw another thing I could make better
- I could have tried to to all of this the first time ...
- but it might have taken me longer to come up with working code
- By getting something working and then making it better ...
- I wrote something that worked very quickly ...
- which gave me the opportunity of making improvements ...
- at my convenience
- The process of writing a first version of the code that works ...
- but maybe does not have all the features you want ...
- and then making small improvements is called stepwise refinement
- Working this way is a very good habit to get into
- You should consider using it while working on your assignments
- In a previous semester some students approached me ...
- with problems they were having with a homework assignment
- Looking at their code I could see that they were trying do too much ...
- all at once
- The assignment called two functions
- print_fahrenheit
- print_conversion_table

- I suggested that the students break the problem down
- The first step was to get print_fahrenheit working
- This function was supposed to accept a Celsius temperature as its parameter
- and print a line with the Celsius temperature ...
- and the corresponding Fahrenheit value
10 50

- I suggested that they write a first version of print_fahrenheit
- and then test it with a function call
- They can do all this in a few lines of code
- Which means if they get an error ...
- they don't have to look far to find it
- Once they got this working ...
- they could start on the next function
- When that function was working ...
- they could write the main body of the code
- By proceeding in this fashion ...
- a big task can be broken down ...
- into a series of manageable steps

- We have seen that a function can return a values that are
- Integers
- Floats
- Strings

- They can also return boolean values
- Such functions are often very simple
- They consist of one line ...
- a
`return`

statement with boolean expression - Here is a boolean function that returns
`True`

... - if the second number evenly divides the first
# returns true if the second number evenly divides the first def evenly_divides(numerator, denominator): remainder = numerator % denominator return remainder == 0

- When we use this in a script with the following code
number_1 = int(input("First number: ")) number_2 = int(input("Second number: ")) if evenly_divides(number_1, number_2): print(number_2, "evenly divides", number_1) else: print(number_2, "does not evenly divide", number_1)

- we get
$ python3 evenly_divides_1.py First number: 87 Second number: 3 3 evenly divides 87 $ python3 evenly_divides_1.py First number: 94 Second number: 7 7 does not evenly divide 94

- We can make the function evenly_divides even shorter ...
- by putting the calculation using the remainder operator % ...
- in the boolean expression after
`return`

# returns true if the second number evenly divides the first def evenly_divides(numerator, denominator): return numerator % denominator == 0

- I don't have to use parentheses around the remainder calculation ...
- because the remainder operator % ...
- has higher precedence that the equality operator ==
- Running this script we get
$ python3 evenly_divides_2.py First number: 87 Second number: 3 3 evenly divides 87 $ python3 evenly_divides_2.py First number: 94 Second number: 7 7 does not evenly divide 94

- Which is the same result we got from the first version

- Boolean functions are useful when performing data validation
- Let's say you need the user to enter a number greater than zero
- You will create a
`while`

loop that will keep asking the user for a number ... - until it gets a number greater than 0
- You need a boolean function to check each entry from the user
- Here is one such function
# returns true if a number is positive def is_positive(number): return number > 0

- Of course we also need a function to get an integer from the user
# prompts for a number and returns an integer def get_number(): return int(input("Please enter a number greater than 0: "))

- Now we need a data validation loop
- We have the function is_positive that tells us when the input is OK
- But the loop should only run when the entry is
**not**OK - So here is the code outside the functions
`numb = get_number() while not is_positive(numb): print("The number you entered is not greater than 0") numb = get_number() print("You entered", numb)`

- When we run this script we get
$ python3 positive_1.py Please enter a number greater than 0: -1 The number you entered is not greater than 0 Please enter a number greater than 0: 0 The number you entered is not greater than 0 Please enter a number greater than 0: 4 You entered 4

- But why should the validation loop be in the body of the script?
- We might need more than one positive number
- So let's write a function that asks for a positive number ...
- and keeps asking until it gets one
# gets a positive number def get_positive_number(): numb = get_number() while not is_positive(numb): print("The number you entered is not greater than 0") numb = get_number() return numb

- When we run this script we get
$ python3 positive_2.py Please enter a number greater than 0: -3 The number you entered is not greater than 0 Please enter a number greater than 0: 0 The number you entered is not greater than 0 Please enter a number greater than 0: 8 Your positive number is 8

- In the section on boolean function above I created the script evenly_divides_2.py ...
- which contained the following two statements
number_1 = int(input("First number: ")) number_2 = int(input("Second number: "))

- Although the two statements are different ...
- they are similar enough that we are essentially repeating ourselves
- So we need to create a function like this
# prompts the user and returns an integer def get_integer(prompt): return int(input(prompt + ": "))

- and we can call this function twice
number_1 = get_integer("First number") number_2 = get_integer("Second number")

- You can see the full script here
- This is a useful function and we might want to use it in other scripts
- We can certainly copy and paste it into another program ...
- but there is a better way
- We can create a module
- A module is a file containing Python statements ...
- usually function definitions and constants ...
- that can be used by other programs
- The filename of the module must
- End in .py
- The module name cannot be a keyword

- If we put the function get_integer into a file named utilities.py ...
- any program that needs to use the module's function can import the module
import utilities

- The script can then call the function using dot notation
number_1 = utilities.get_integer("First number") number_2 = utilities.get_integer("Second number")