Writing Functions
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.
- 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.
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).
- If you don't name the arguments when using them in the call, the arguments will be matched to parameters in the order the parameters are defined in 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
Or, we can name the arguments when we call the function, which allows us to
specify them in any order:
print_date(month=3, day=19, year=1871)
1871/3/19
- Arguments matched by their position are called positional arguments.
- Arguments matched by their name, like
month=3, are called keyword arguments (or named arguments).
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
returnoccurs:- At the start to handle special cases.
- At the very end, with a final result.
def average(values): if len(values) == 0: return None return sum(values) / len(values)
a = average([1, 3, 4]) print('average of actual values:', a)
average of actual values: 2.6666666666666665
print('average of empty list:', average([]))
average of empty list: None
- Remember: every function returns something.
- A function that doesn't explicitly
returna value automatically returnsNone.
result = print_date(1871, 3, 19) print('result of call is:', result)
1871/3/19 result of call is: None
Parameters can have default values
- Give a parameter a default value in the definition with
name=value. - If the caller doesn't pass that argument, the default is used.
- Parameters with defaults must come after parameters without them.
def say_hello(name='World'): return 'Hello, ' + name + '!' print(say_hello()) print(say_hello('Ada')) print(say_hello(name='Grace'))
Hello, World! Hello, Ada! Hello, Grace!
- Defaults are useful for options that usually have a sensible value, but that callers sometimes want to change.
- Built-in functions use them too:
help(round)showsround(number, ndigits=None). - When you mix the two kinds of argument in a call, positional arguments must come first.
print(say_hello(name='Ada', 'Grace'))
SyntaxError: positional argument follows keyword argument
Custom Greetings
Write a new version of
say_hello with two parameters, greeting and name, which default to 'Hello' and 'World'.
How many different ways can you call it using combinations of positional and keyword arguments?Functions can call other functions
- A function hides how a calculation is done, so other code can use it without worrying about the details.
- That includes other functions: build bigger functions out of smaller ones. This is called composition.
- For example, converting Fahrenheit to Kelvin goes through Celsius:
def fahr_to_cels(fahr): cels = (fahr - 32) * (5 / 9) return cels def fahr_to_kelv(fahr): cels = (fahr - 32) * (5 / 9) kelv = cels + 273.15 return kelv print(fahr_to_kelv(32)) print(fahr_to_kelv(212))
273.15 373.15
- The Fahrenheit-to-Celsius formula appears twice. If we found a typo in one copy, we'd have to remember to fix the other.
- Call
fahr_to_celsfromfahr_to_kelvinstead, so the formula lives in one place:
def fahr_to_cels(fahr): return (fahr - 32) * (5 / 9) def fahr_to_kelv(fahr): return fahr_to_cels(fahr) + 273.15 print(fahr_to_kelv(212))
373.15
- Each function now does one small job, which makes it easier to read and to check.
Combining Strings
"Adding" two strings joins them:
'a' + 'b' is 'ab'.- Write a function
fence(original, wrapper)that returnsoriginalwithwrapperadded at the start and end, sofence('name', '*')returns'*name*'. - Using
fence, write a functiondouble_fence(original, inner, outer), so thatdouble_fence('name', '-', '*')returns'*-name-*'.
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
SyntaxErroror 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.")
Definition and Use
What does the following program print?
def report(pressure): print('pressure is', pressure) print('calling', report, 22.5)
Order of Operations
- What's wrong in this example?
result = print_time(11, 37, 59) def print_time(hour, minute, second): time_string = str(hour) + ':' + str(minute) + ':' + str(second) print(time_string) - After fixing the problem above, explain why running this example code:
result = print_time(11, 37, 59) print('result of call is:', result)gives this output:11:37:59 result of call is: None - Why is the result of the call
None?
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
Earlier we saw this function:
def print_date(year, month, day): joined = str(year) + '/' + str(month) + '/' + str(day) print(joined)
We saw that we can call the function using named arguments, like this:
print_date(day=1, month=2, year=2003)
- What does
print_date(day=1, month=2, year=2003)print? - When have you seen a function call like this before?
- When and why is it useful to call functions this way?
Encapsulation of an 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 encapsulated in 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 significant. 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.
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. A canonical
example of a dynamical system is the logistic map,
a growth model that computes a new population density (between 0 and 1) based on the current
density. In the model, time takes discrete values 0, 1, 2, ...
- Define a function called
logistic_mapthat takes two inputs:x, representing the current population (at timet), and a parameterr = 1. This function should return a value representing the state of the system (population) at timet + 1, using the mapping function:f(t+1) = r * f(t) * [1 - f(t)] - Using a
fororwhileloop, iterate thelogistic_mapfunction defined in part 1, starting from an initial population of 0.5, for a period of timet_final = 10. Store the intermediate results in a list so that after the loop terminates you have accumulated a sequence of values representing the state of the logistic map at timest = [0,1,...,t_final]. Print this list to see the evolution of the population. - Encapsulate the logic of your loop into a function called
iteratethat takes the initial population as its first input, the parametert_finalas its second input and the parameterras its third input. The function should return the list of values representing the state of the logistic map at timest = [0,1,...,t_final]. Run this function for periodst_final = 100and1000and print some of the values. Is the population trending toward a steady state?