Writing Functions

NoteQuestions
  • How do I create my own reusable functions?
  • How do arguments and return values work?
NoteObjectives
  • Define a function using def with parameters and a body.
  • Distinguish between defining a function and calling it.
  • Call a function with positional and named arguments.
  • Return a value from a function using return.

Functions make programs manageable

When a program grows beyond a few dozen lines, keeping track of every detail becomes impractical. Just as variables make it easier to reuse values throughout your code, Functions make it easier to reuse `methods’ throughout your code. Functions let you bundle a piece of logic/code with a name, then use that name throughout your code when you want to use that bundle of. A well-named function also makes code dramatically easier to read, and it enables re-use.

Defining a function

A function definition begins with the keyword def, followed by the function name, its parameters in parentheses, and a colon. The indented block that follows is the body — the code that runs every time the function is called:

def print_greeting():
    print('Hello!')

Defining a function does not run it. Think of def as storing the bundle for later. The function executes only when you call it:

print_greeting()
Hello!

Parameters and arguments

A function that always does the same thing is limited. Parameters make a function general: they are placeholder names that receive the values passed by the caller (the arguments) at call time. If you do not name the arguments in the call, they are matched to parameters in the order they are defined:

def print_date(year, month, day):
    joined = str(year) + '/' + str(month) + '/' + str(day)
    print(joined)

print_date(1871, 3, 19)
1871/3/19

You can also pass arguments by name (keyword arguments), which lets you specify them in any order:

print_date(month=3, day=19, year=1871)
1871/3/19

You have already used keyword arguments — numpy.loadtxt('file.csv', delimiter=',', skiprows=1) uses them for delimiter and skiprows.

Returning values

Use return to send a value back to the caller. The function stops executing immediately when it reaches a return statement. Functions that do not include an explicit return automatically return None:

def average(values):
    if len(values) == 0:
        return None
    return sum(values) / len(values)
print('average of actual values:', average([1, 3, 4]))
average of actual values: 2.6666666666666665
print('average of empty list:', average([]))
average of empty list: None

print_date from earlier is an example of a function that returns None — its job is the side effect of printing, not producing a value:

result = print_date(1871, 3, 19)
print('result of call is:', result)
1871/3/19
result of call is: None

The function must also be defined before it is called. Python executes files from top to bottom, so calling a function before its def statement raises a NameError.

CautionChallenge

Identifying Syntax Errors

  1. Read the code below and identify the errors without running it.
  2. Run it and read the error message — is it a SyntaxError or an IndentationError?
  3. Fix all 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.")

The function definition is missing both the parentheses after the name and the colon. The second print is over-indented. Corrected:

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.")
CautionChallenge

Definition and Use

What does the following program print?

def report(pressure):
    print('pressure is', pressure)

print('calling', report, 22.5)
calling <function report at 0x7fd646ed5580> 22.5
calling <function report at 0x7fd128ff1bf8> 22.5

Without parentheses, report refers to the function object itself rather than calling it. The correct way to call the function and then print is:

print("calling")
report(22.5)
calling
pressure is 22.5
CautionChallenge

Order of Operations

The example below prints two lines. Explain why they appear in that order. What is wrong with the second code block?

result = print_date(1871, 3, 19)
print('result of call is:', result)
1871/3/19
result of call is: None
result = print_date(1871, 3, 19)   # call comes before the definition

def print_date(year, month, day):
    joined = str(year) + '/' + str(month) + '/' + str(day)
    print(joined)
1871/3/19

The first block: all the code inside print_date runs before print_date returns, so the date is printed first and result of call is: None second.

The second block fails because Python reads the file from top to bottom. When it reaches the call on line 1, print_date has not been defined yet — a NameError results. Always define functions before calling them.

CautionChallenge

Encapsulation

Fill in the blanks to create a function that loads a CSV file and returns its minimum value:

import numpy

def min_in_data(____):
    data = ____
    return ____
import numpy

def min_in_data(filename):
    data = numpy.genfromtxt(filename, delimiter=',', skip_header=1)
    return numpy.nanmin(data)
CautionChallenge

Find the First

Fill in the blanks to create a function that returns the first negative value in a list. What does your function return if the list is empty?

def first_negative(values):
    for v in ____:
        if ____:
            return ____
def first_negative(values):
    for v in values:
        if v < 0:
            return v

If the list is empty (or contains no negative values), the function reaches the end without hitting a return and implicitly returns None.

CautionChallenge

Calling by Name

We saw that print_date can be called with keyword arguments:

print_date(day=1, month=2, year=2003)
2003/2/1
  1. What does that call print?
  2. Where else have you seen keyword arguments in these lessons?
  3. When is using keyword arguments especially valuable?
  1. 2003/2/1
  2. In numpy.loadtxt — arguments like delimiter=',' and skiprows=1 are keyword arguments.
  3. When a function has many parameters and positional order is easy to mix up, naming arguments makes the call self-documenting and prevents subtle ordering mistakes.
TipKey Points
  • Use def name(parameters): to define a function; the indented body is the code that runs.
  • Defining a function does not run it — call it by name with parentheses.
  • Arguments in a call are matched to parameters by position; keyword arguments can be passed in any order.
  • Use return to send a result back to the caller; a function without return returns None.
  • Functions must be defined before they are called.