Writing Functions
Overview
Teaching: 15 min
Exercises: 10 minQuestions
How can I create my own functions?
Objectives
Explain and identify the difference between function definition and function call.
Write a function that takes a small, fixed number of arguments and produces a single result.
Break programs down into functions to make them easier to understand.
- Human beings can only keep a few items in working memory at a time.
- Understand larger/more complicated ideas by understanding and combining pieces.
- Components in a machine.
- Lemmas when proving theorems.
- Best Practice: Write programs for people, not computers
- Functions serve the same purpose in programs.
- Encapsulate complexity so that we can treat it as a single “thing”.
- Also enables re-use.
- Write one time, use many times.
- Best Practice: Don’t repeat yourself
Define a function using def
with a name, parameters, and a block of code.
- Begin the definition of a new function with
def
. - Followed by the name of the function.
- Must obey the same rules as variable names.
- Then parameters in parentheses.
- Empty parentheses if the function doesn’t take any inputs.
- We will discuss this in detail in a moment.
- Then a colon.
- Then an indented block of code.
def print_greeting():
print('Hello!')
Defining a function does not run it.
- Defining a function does not run it.
- Like assigning a value to a variable.
- Must call the function to execute the code it contains.
print_greeting()
Hello!
Arguments in call are matched to parameters in definition.
- Functions are most useful when they can operate on different data.
- Specify parameters when defining a function.
- These become variables when the function is executed.
- Are assigned the arguments in the call (i.e., the values passed to the function).
def print_date(year, month, day):
joined = str(year) + '/' + str(month) + '/' + str(day)
print(joined)
print_date(1871, 3, 19)
1871/3/19
- Via Twitter:
()
contains the ingredients for the function while the body contains the recipe.
Functions may return a result to their caller using return
.
- Use
return ...
to give a value back to the caller. - May occur anywhere in the function.
- But functions are easier to understand if
return
occurs:- At the start to handle special cases.
- At the very end, with a final result.
def average(values):
return sum(values) / len(values)
a = average([1, 3, 4])
print('average of actual values:', a)
2.6666666666666665
print('average of empty list:', average([]))
None
- Remember: every function returns something.
- A function that doesn’t explicitly
return
a value automatically returnsNone
.
result = print_date(1871, 3, 19)
print('result of call is:', result)
1871/3/19
result of call is: None
Our data analysis
def plot_gdp(country_file, country):
data = pandas.read_csv(country_file, index_col='country')
data.loc[country,:].plot()
plt.ylabel('GDP per capita')
plt.xticks(rotation=90)
plot_gdp('data/'data/gapminder_gdp_oceania.csv')
Identifying Syntax Errors
- Read the code below and try to identify what the errors are without running it.
- Run the code and read the error message. Is it a
SyntaxError
or anIndentationError
?- Fix the error.
- Repeat steps 2 and 3 until you have fixed all the errors.
def another_function print("Syntax errors are annoying.") print("But at least python tells us about them!") print("So they are usually not too hard to fix.")
Solution
def another_function(): print("Syntax errors are annoying.") print("But at least Python tells us about them!") print("So they are usually not too hard to fix.")
Definition and Use
What does the following program print?
def report(pressure): print('pressure is', pressure) print('calling', report, 22.5)
Order of Operations
The example above:
result = print_date(1871, 3, 19) print('result of call is:', result)
printed:
1871/3/19 result of call is: None
Explain why the two lines of output appeared in the order they did.
Find the First
Fill in the blanks to create a function that takes a list of numbers as an argument and returns the first negative value in the list. What does your function do if the list is empty?
def first_negative(values): for v in ____: if ____: return ____
Calling by Name
What does this short program print?
def print_date(year, month, day): joined = str(year) + '/' + str(month) + '/' + str(day) print(joined) print_date(day=1, month=2, year=2003)
- When have you seen a function call like this before?
- When and why is it useful to call functions this way?
Encapsulate of If/Print Block
The code below will run on a label-printer for chicken eggs. A digital scale will report a chicken egg mass (in grams) to the computer and then the computer will print a label.
Please re-write the code so that the if-block is folded into a function.
import random for i in range(10): # simulating the mass of a chicken egg # the (random) mass will be 70 +/- 20 grams mass=70+20.0*(2.0*random.random()-1.0) print(mass) #egg sizing machinery prints a label if(mass>=85): print("jumbo") elif(mass>=70): print("large") elif(mass<70 and mass>=55): print("medium") else: print("small")
The simplified program follows. What function definition will make it functional?
# revised version import random for i in range(10): # simulating the mass of a chicken egg # the (random) mass will be 70 +/- 20 grams mass=70+20.0*(2.0*random.random()-1.0) print(mass,print_egg_label(mass))
- Create a function definition for
print_egg_label()
that will work with the revised program above. Note, the function’s return value will be signifigant. Sample output might be71.23 large
.- A dirty egg might have a mass of more than 90 grams, and a spoiled or broken egg will probably have a mass that’s less than 50 grams. Modify your
print_egg_label()
function to account for these error conditions. Sample output could be25 too light, probably spoiled
.
Encapsulating Data Analysis
Assume that the following code has been executed:
import pandas df = pandas.read_csv('gapminder_gdp_asia.csv', index_col=0) japan = df.ix['Japan']
- Complete the statements below to obtain the average GDP for Japan across the years reported for the 1980s.
year = 1983 gdp_decade = 'gdpPercap_' + str(year // ____) avg = (japan.ix[gdp_decade + ___] + japan.ix[gdp_decade + ___]) / 2
- Abstract the code above into a single function.
def avg_gdp_in_decade(country, continent, year): df = pd.read_csv('gapminder_gdp_'+___+'.csv',delimiter=',',index_col=0) ____ ____ ____ return avg
- How would you generalize this function if you did not know beforehand which specific years occurred as columns in the data? For instance, what if we also had data from years ending in 1 and 9 for each decade? (Hint: use the columns to filter out the ones that correspond to the decade, instead of enumerating them in the code.)
Solution
1.
year = 1983 gdp_decade = 'gdpPercap_' + str(year // 10) avg = (japan.ix[gdp_decade + '2'] + japan.ix[gdp_decade + '7']) / 2
2.
def avg_gdp_in_decade(country, continent, year): df = pd.read_csv('gapminder_gdp_' + continent + '.csv', index_col=0) c = df.ix[country] gdp_decade = 'gdpPercap_' + str(year // 10) avg = (c.ix[gdp_decade + '2'] + c.ix[gdp_decade + '7'])/2 return avg
- We need to loop over the reported years to obtain the average for the relevant ones in the data.
def avg_gdp_in_decade(country, continent, year): df = pd.read_csv('gapminder_gdp_' + continent + '.csv', index_col=0) c = df.ix[country] gdp_decade = 'gdpPercap_' + str(year // 10) total = 0.0 num_years = 0 for yr_header in c.index: # c's index contains reported years if yr_header.startswith(gdp_decade): total = total + c.ix[yr_header] num_years = num_years + 1 return total/num_years
Simulating a dynamical system
In mathematics, a dynamical system is a system in which a function describes the time dependence of a point in a geometrical space. Canonical example of a dynamical system is a system called the logistic map.
Define a function called
logistic_map
that takes two inputs:X
, representing the state of the system at time t, and a parameterr
. This function should return a value representing the state of the system at time t+1.Using a
for
loop, iterate thelogistic_map
function defined in part 1 starting from an initial condition of 0.5 forT=10
,100
, and1000
periods. Store the intermediate results in a list so that after thefor
loop terminates you have accumulated a sequence of values representing the state of the logistic map at time t=0,1,…,T.Encapsulate the logic of your
for
loop into a function callediterate
that takes the initial condition as its first input, the parameterT
as its second input and the parameterr
as its third input. The function should return the list of values representing the state of the logistic map at time t=0,1,…,T.Solution
1.
def logistic_map(X, r): return r * X * (1 - X)
2.
initial_condition = 0.5 T = 10 r = 1.0 trajectory = [initial_condition] for t in range(1, T): trajectory[t] = logistic_map(trajectory[t-1], r)
3.
def iterate(initial_condition, T, r): trajectory = [initial_condition] for t in range(1, T): trajectory[t] = logistic_map(trajectory[t-1], r) return trajectorys
Key Points
Break programs down into functions to make them easier to understand.
Define a function using
def
with a name, parameters, and a block of code.Defining a function does not run it.
Arguments in call are matched to parameters in definition.
Functions may return a result to their caller using
return
.