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: Function Groups
Programming for Data Science
In this notebook, we illustrate an import concept of function design.
We show how to design groups of functions that can break down a complex process into simple components.
Building on the idea that functions should do one thing, or something simple, think of each function as performing a kind task that is part of a larger problem.
A task may getting data from a user, applying a formula to some data, connecting to a database, or presenting results to a user.
Other functions may perform integrative work — for example, they may call simpler functions and integrate them into a sequence.
We can think of this a division of labor, or separation of concerns, among functions.
Groups of functions associated with a common process may interact in various ways:
By having functions call functions.
By defining functions within functions.
By chaining — where the return value of one function becomes the argument of another.
By sharing global varables.
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.
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 keys.
The keys represent the target temperature scale to which we are converting.
Essentially, we 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
# Convert from one scale to the other
= converters[target_scale](source_temp)
target_temp
# Get the source scale for display purposes
= list(set(converters.keys()) - set(target_scale))[0]
source_scale
# Output to user
print(source_temp, source_scale, "converted becomes:" , round(target_temp), target_scale)
convert_app2()
Enter a temperature: 45
Enter the scale to convert to: (c or f) f
45 c converted becomes: 113 f
Example 2: Counting Vowels
Here is another example of functions calling each other in increasing levels of complexity.
def is_vowel(letter):
"Tests if a letter is a vowel."
return letter in "aeiou"
def num_vowels(my_string):
"Counts the number of vowels in a 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
# A more concise way:
# return sum([is_vowel(letter) for letter in my_string.lower()])
def vowel_counter():
"User interface to return the number of vowels in a given string."
= input("Enter a string: ")
my_str = num_vowels(my_str)
vcount print(f"There are {vcount} vowels in the string.")
vowel_counter()
Enter a string: This is a string.
There are 4 vowels in the string.
Example 3: Calculating Tax
In this example, we define a group of functions to perform a more complicated task. We want to compute the taxes owed for an income and a tax rate. We want our user to enter an income and to get their tax bill back.
To compute tax, we have these data:
gross_pay tax_rate
---------------------
0 - 240 0%
241 - 480 15%
481 - more 28%
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 write 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 meet these requirements, we create a group of functions that expect some global variables to exist and use these instead of return statements.
def compute_tax():
"""
Computes tax rate and applies to gross pay to get tax.
Expects gross_pay 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()
Enter your gross pay in dollars: 1000000
Based on a tax rate of 28%, the tax you owe on $1000000 is $280000.
get_net_pay()
Your take home (net) pay is $720000.
do_all()
Enter your gross pay in dollars: 1000000
Based on a tax rate of 28%, the tax you owe on $1000000 is $280000.
Your take home (net) pay is $720000.
Notice how none of the functions return a value.
Nor do they take arguments.
Instead, they give and take from the global namespace.
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.
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.