def f_to_c(temp):
"""
Converts F to C and returns a rounded result.
Expects an integer and returns an integer.
"""
return round((temp - 32) * (5/9))
def c_to_f(temp):
"""
Converts C to F and returns a rounded result.
Expects an integer and returns an integer.
"""
return round(temp * (9/5) + 32)
def convert(temp, scale):
"""
Combines conversion functions into a two-way converter.
Expects a souce temp (int) and a target scale ('f' or 'c').
"""
if scale.lower() == "c":
return f_to_c(temp) # function call to f_to_c
else:
return c_to_f(temp) # function call to c_to_f
NB: Functions Calling Functions
Purpose: * Illustrate concept of function design * Demonstrate how functions can break down a process into simple components * Demonstrate how component functions build on each other * Introduce idea of functional groups * Motivate use of classes (to be introduced later)
Basic Insight
Functions contain any code, so they can contain functions. * Functions can call other functions * Functions can define new functions
We create functions that call functions in order to break a complex process into components. * Some functions focus on simple component processes * Other functions combine these into higher order processes * Some functions may be focused on computation, while others may be focused on interacting with users or data sources * We can think of this a division of labor, or “separation of concerns,” among functions
When you create groups of functions, they often form natural groups that associated with a common process or task. * These function groups often share variables in addition to calling each ohter
Let’s look at some examples to illustrate these points.
Example 1: Converting Temperatures
Here are three functions that work together to make a temperature converter.
Notice how the last function integrates the first two.
Now, here is function that combines the above functions into a user-facing interface to the other functions.
##| tags: []
def convert_app():
"""
Provides a user-interface to the the conversion functions.
"""
# Get user input
= int(input("Enter a temperature: "))
temp = input("Enter the scale to convert to: (c or f) ")[0].lower()
scale
# Infer source scale, to be used in the output message
if scale == 'c':
= 'f'
current_scale else:
= 'c'
current_scale
# Do the conversion
= convert(temp, scale)
converted
# Print results for user
print(f"{temp}{current_scale.upper()} is equal to {converted}{scale.upper()}.")
convert_app()
Enter a temperature: 45
Enter the scale to convert to: (c or f) f
45C is equal to 113F.
A More Pythonic Solution
We replace if/then statements with dictionary logic.
## Put your logic in the data structure
= {
converters 'c': lambda t: (t - 32) * (5/9),
'f': lambda t: t * (9/5) + 32
}
def convert_app2():
# Input from user
= int(input("Enter a temperature: "))
source_temp = input("Enter the scale to convert to: (c or f) ")
target_scale
# Internal computations
= converters[target_scale](source_temp)
target_temp # source_scale = list(set(converters.keys()) - set(target_scale))[0]
= (set(converters.keys()) - set(target_scale)).pop()
source_scale
# Output to user
print(source_temp, source_scale, "converted becomes:" , round(target_temp), target_scale)
convert_app2()
Example 2: Counting Vowels
## Predicate functions - often used as helper functions that return True or False
def is_vowel(l):
if l == "a" or l == "e" or l == "i" or l == "o" or l == "u":
return True # if the letter is a vowel, return True
else:
return False # else, return False
def num_vowels(my_string):
= my_string.lower()
my_string = 0
count for i in range(len(my_string)): # for each character
if is_vowel(my_string[i]): # call function above
+= 1 # increment count if true
count return count
def vcounter():
= input("Enter a string: ")
my_str = num_vowels(my_str)
vcount print(f"There are {vcount} vowels in the string.")
vcounter()
A More Pythonic Solution
We can use a lambda function with a comprehension to replace the fisrt two functions above.
= lambda x: len([char for char in x.lower() if char in "aeiou"]) vowel_count
= "Whatever it is, it is what it is." test_str
vowel_count(test_str)
Example 3: Calculating Tax
We write two related functions: * One to compute the tax based on a gross pay and a tax rate. * One to compute the net pay using the previous function.
In addition, we want to write some functions that use these functions to interact with a user. * One to get the input value of the gross pay and print the tax. * One to print the net pay based on the previous function.
Note the division of labor, or “separation of concerns”, in these functions: * Some do calculative work * Some do interactive work
To compute tax, we have these data:
gross_pay tax_rate
---------------------
0 - 240 0%
241 - 480 15%
481 - more 28%
This time, we want to create a group of functions that expect some global variables to exist and use these instead of return statements. |
In the code below, we globalize any variables that are assigned in our functions. |
This allows them to be shared by all the other functions. |
Note that this is effective when our global environment – the containing script – contains only these functions. |
Later in this course, we will look at mechanisms to segment our code in this way. |
def compute_tax():
"""
Computes tax rate and applies to gross pay to get tax.
Expects gross_apy to be defined globally.
Adds tax_rate and tax to globals for use by other functins.
"""
global tax_rate, tax
# Get rate by lower bound
if gross_pay > 480:
= .28
tax_rate elif gross_pay > 240:
= .15
tax_rate else:
= 0
tax_rate
= gross_pay * tax_rate
tax
def compute_net_pay():
"""
Computes net pay based on globals produced by compute_tax().
Expects gross_pay and tax to be defined globally.
Adds net_pay to to globals.
"""
global net_pay
= gross_pay - tax
net_pay
def get_tax():
"""
Computes and prints tax based on user input.
Essentially a wrapper around compute_tax().
Adds gross_pay to globals.
"""
global gross_pay
= int(input("Enter your gross pay in dollars: "))
gross_pay
compute_tax()
print(f"Based on a tax rate of {round(tax_rate * 100)}%, the tax you owe on ${gross_pay} is ${round(tax)}.")
def get_net_pay():
"""
Computes and prints net pay based on globals.
"""
compute_net_pay()
print(f"Your take home (net) pay is ${round(net_pay)}.")
def do_all():
"Runs both user-facing functions."
get_tax() get_net_pay()
get_tax()
get_net_pay()
do_all()
Concluding Observations
- Notice how each example has functions that build on each other.
- These functions share both data and a general goal.
- The fact that data and functions go together is the motivation for creating classes.