1 of 44

Functions and abstraction

Andrew S. Fitz Gibbon

UW CSE 160

Winter 2024

1

2 of 44

Functions

In math:

  • you use functions: sine, cosine, …
  • you define functions: f(x) = x2 + 2x + 1

Python:

  • Lets you use and define functions
  • We have already seen some Python functions:
    • len, float, int, str, range

2

3 of 44

Python Functions

In Python:

  • A function packages up and names a computation
  • Enables re-use and, through parameters, generalization of the computation to other scenarios
  • Allows you to reduce repetition in your programs
    • Don’t Repeat Yourself (DRY principle)
  • Makes your programs:
    • Shorter
    • Easier to understand
    • Easier to modify and debug

3

Similar to what we saw with loops

4 of 44

Using ("calling") a function

len("hello") len("")

math.sqrt(9) math.sqrt(7)

range(1, 5) range(8)

math.sin(0) str(17)

  • Some need no input: random.random()
  • All of the functions above return a value
  • We did not have to write these functions ourselves! We get to reuse code someone else wrote.

4

5 of 44

Function call examples

import math

x = 8

y = 16

z = math.sqrt(16)

u = math.sqrt(y)

v = math.sqrt(8 + 8)

w = math.sqrt(x + x)

greeting = "hi"

name = "Fitz"

a = len("hello")

b = len(greeting)

c = len("hello" + "Fitz")

d = len(greeting + name)

print("hello")

print()

print(len(greeting + name))

What are the:

  • Function calls or �“function invocations” or “call sites”?
  • arguments or �actual parameters �for each function call?

  • math.sqrt and len take input and return a value.
  • print produces a side effect (it prints to the terminal).

5

6 of 44

Some functions are like a machine

  • You give it input
  • It produces a result, “returns” a value

6

2x + 1

x

2x + 1

x

2x + 1

x

100

0

2

5

201

1

In math: func(x) = 2x + 1

7 of 44

Defining a function

Define the machine,�including the input and the result

7

def dbl_plus(x):

return 2 * x + 1

Keyword that means:�I am defining a function

Keyword that means:�This is the result

Input variable name,�or “formal parameter”

Name of the function.�Like “y = 5” for a variable

2x + 1

x

Return expression�(part of the return statement)

8 of 44

How Python executes a function call

  1. Evaluate the argument(s) at the call site – the place where we are calling the function from in our program
  2. Assign the argument’s value to the formal parameter name
    • A new variable, not a reuse of any existing variable of the same name
  3. Evaluate the statements in the body of the function one by one
  4. At a return statement:
    • Formal parameter variable disappears – exists only during the call!
    • The call expression evaluates to the “returned” value

8

Formal parameter (a variable)

def square(x):

return x * x

square(3 + 4)

Function definition

Function call or�function invocation, �the “call site”

Example function call:

y = 1 + square(3 + 4)

y = 1 + square(7)

Variables:

x: 7

return x * x

return 7 * x

return 7 * 7

return 49

Argument or �“actual parameter”

y = 1 + 49

y = 50

9 of 44

Practice Reading Functions

x = 1

z = 3.1

def multiply(x, y):

# (1) What are the values of x and y here

# the first time multiply is called?

print("x,y:", x, y)

z = 0

return x * y

x = 2

y = 1

result = multiply(3, y)

print(y) # (2) What is the value of y here?

result = multiply(x, 4)

print(result) # (3) What is the value of result here?

print(z) # (4) What is the value of z here?

9

10 of 44

Function definition examples

def dbl_plus(x):

return 2 * x + 1

def instructor_name():

return "Andrew Fitz Gibbon"

def square(x):

return x * x

def calc_grade(points):

grade = points * 10

return grade

10

For each function definition, identify:

  • Function name
  • Function body
  • formal parameters

11 of 44

Function definitions and calls

def dbl_plus(x):

return 2 * x + 1

def instructor_name():

return "Andrew Fitz Gibbon"

def calc_grade(points):

grade = points * 10

return grade

# main program

dbp3 = dbl_plus(3)

dbp4 = dbl_plus(4)

print(dbp3 + dbp4)

print(instructor_name())

my_grade = calc_grade(dbp3)

11

Identify:

  • Function definitions
  • formal parameters

  • Function calls or �“function invocations” or “call sites”?
  • arguments or �actual parameters?

This is all in the same file

12 of 44

More function definitions and calls

def square(x):

return x * x

def print_greeting():

print("Hello, world")

def calc_grade(points):

grade = points * 10

return grade

def print_grade(points):

grade = points * 10

print("Grade is:", grade)

# main program

sq1 = square(3)

print_greeting()

my_grade = calc_grade(sq1)

print_grade(5)

12

No return statement

Returns the value None

Executed for side effect

No return statement

Returns the value None

Executed for side effect

This is all in the same file

13 of 44

How many x variables?

def square(x):

return x * x

def abs(x):

if x < 0:

return –x

else:

return x

# main program

x = 42�sq3 = square(3)�sq4 = square(4)�print(sq3 + sq4)�print(x)�x = -22�result = abs(x)�print(result)

13

This is all in the same file

14 of 44

Functions can call functions

def fahr_to_cent(fahr):

return (fahr – 32) / 9.0 * 5

def cent_to_fahr(cent):

result = cent / 5.0 * 9 + 32

return result

def print_fahr_to_cent(fahr):

result = fahr_to_cent(fahr)

print(result)

# main program

boiling = fahr_to_cent(212)

cold = cent_to_fahr(-30)

print(print_fahr_to_cent(32))

14

This is all in the same file

No return statement

Returns the value None

Executed for side effect

15 of 44

Two types of output

  • An expression evaluates to a value
    • Which can be used by the containing expression or statement
  • A print statement writes text to the screen

  • The Python interpreter (used the first week of class) reads statements and expressions, then executes them, like a calculator
  • If the interpreter executes an expression, it prints its value

  • In a program (VSCode, Python Tutor), evaluating an expression does not print it
  • In a program, printing an expression does not permit it to be used elsewhere

15

16 of 44

In a function body, assignment creates a temporary variable (like the formal parameter)

def store_it(arg):

stored = arg

return stored

stored = 0

y = store_it(22)

print(y)

print(stored)

16

17 of 44

How to look up a variable

Idea: find the nearest variable of the given name

    • Check whether the variable is defined in the local scope
    • … Check any intermediate scopes (none in CSE 160!) …
    • Check whether the variable is defined in the global scope

If a local and a global variable have the same name, the global variable is inaccessible (“shadowed” or “masked”)

This is confusing; try to avoid shadowing

x = 22

stored = 100

def lookup():

x = 42

return stored + x

val = lookup()

x = 5

stored = 200

val = lookup()

17

def lookup():

x = 42

return stored + x

x = 22

stored = 100

val = lookup()

x = 5

stored = 200

val = lookup()

What happens if we define stored after lookup?

18 of 44

Local variables exist �only while the function is executing

def cent_to_fahr(cent):

result = cent / 5.0 * 9 + 32

return result

tempf = cent_to_fahr(15)

print(result)

18

19 of 44

Use only the local and the global scope!

myvar = 1

def outer():

myvar = 1000

temp = inner()

return temp

def inner():

return myvar

print(outer())

19

20 of 44

Functions are an Abstraction

  • Abstraction = ignore some details
  • Generalization = become usable in more contexts
  • Abstraction over computations:
    • Functional abstraction, a.k.a. procedural abstraction
  • As long as you know what the function means, you don’t care how it computes that value
    • You don’t care about the implementation (the function body)

20

21 of 44

Defining absolute value

def abs(x):

if x < 0:

return -1 * x

else:

return 1 * x

def abs(x):

if x < 0:

return -x

else:

return x

def abs(x):

if x < 0:

result = -x

else:

result = x

return result

def abs(x):

return math.sqrt(x * x)

21

22 of 44

Defining round�(for positive numbers)

def round(x):

return int(x + 0.5)

def round(x):

fraction = x - int(x)

if fraction >= 0.5:

return int(x) + 1

else:

return int(x)

22

23 of 44

Two types of documentation

  1. Documentation for users/clients/callers
    • Document the purpose or meaning or abstraction that the function represents
    • Often called the “docstring”
    • Tells what the function does
    • Should be written for every function
  2. Documentation for programmers who are reading the code
    • Document the implementation – specific code choices
    • Tells how the function does it
    • Only necessary for tricky or interesting bits of the code

23

24 of 44

Two types of documentation

24

For programmers: arbitrary text after #

For users: a string as the first element of the function body

def square(x):

"""Returns the square of its argument."""

# Uses "x*x" instead of "x**2"

return x * x

25 of 44

Multi-line strings

  • Ways to write strings:
    • "hello"
    • 'hello'
    • """hello"""
    • '''hello'''
  • Triple-quote version:
    • can include newlines (carriage returns),�so the string can span multiple lines
    • can include quotation marks
    • Use """hello""" version for docstings

25

26 of 44

Don’t write useless comments

  • Comments should give information that is not apparent from the code
  • Here is a counter-productive comment that merely clutters the code, which makes the code harder to read:

# increment the value of x

x = x + 1

26

DO NOT write comments like this.

27 of 44

Where to write comments

  • By convention, write a comment before the code that it describes (rarely on the same line)
    • First a reader sees the English explanation for why the code is the way it is, then the possibly-confusing code

# We count down to 0 here because our users expect

# results to be in descending order.

for i in range(5, -1, -1):

...

  • A comment may appear anywhere in your program, including at the end of a line:

x = y + x # a comment about this line

  • For a line that starts with #, indentation should be consistent with surrounding code

27

28 of 44

Decomposing a problem

  • Breaking down a program into functions is the fundamental activity of programming!
  • How do you decide when to use a function?
    • One rule: DRY (Don’t Repeat Yourself)
    • Whenever you are tempted to copy and paste code, don’t!
  • Now, how do you design a function?

28

29 of 44

How to design a function

1. Wishful thinking: Write the program as if the function already exists

2. Write a specification: Describe the inputs and output, including their types

No implementation yet!

3. Write tests: Example inputs and outputs

4. Write the function body (the implementation)

First, write your plan in English, then translate to Python

def fahr_to_cent(fahr):

"""

Input: a number representing degrees Fahrenheit

Return value: a number representing degrees centigrade

"""

result = (fahr – 32) / 9.0 * 5

return result

assert fahr_to_cent(32) == 0

assert fahr_to_cent(212) == 100

assert fahr_to_cent(98.6) == 37

assert fahr_to_cent(-40) == -40

# Main program

tempf = 32

print("Temperature in Fahrenheit:", tempf)

tempc = fahr_to_cent(tempf)

print("Temperature in Celsius:", tempc)

29

30 of 44

More Examples

def cent_to_fahr(cent):

print(cent / 5.0 * 9 + 32)

print(cent_to_fahr(20))

def c_to_f(c):

print "c_to_f"

return c / 5.0 * 9 + 32

def make_message(temp):

print("make_message")

return "The temperature is " + str(temp)

for tempc in [-40, 0, 37]:

tempf = c_to_f(tempc)

message = make_message(tempf)

print(message)

30

def myfunc(n):

total = 0

for i in range(n):

total = total + i

return total

print(myfunc(4))

double(7)

Use the Python Tutor: http://pythontutor.com/

abs(-20 - 2) + 20

31 of 44

What does this print?

def cent_to_fahr(cent):

print(cent / 5.0 * 9 + 32)

print(cent_to_fahr(20))

31

32 of 44

What does this print?

def myfunc(n):

total = 0

for i in range(n):

total = total + i

return total

print(myfunc(4))

32

33 of 44

What does this print?

def c_to_f(c):

print("c_to_f")

return c / 5.0 * 9 + 32

def make_message(temp):

print("make_message")

return "The temperature is " + str(temp)

for tempc in [-40, 0, 37]:

tempf = c_to_f(tempc)

message = make_message(tempf)

print(message)

33

34 of 44

What does this print?

def c_to_f(c):

print("c_to_f”)

return c / 5.0 * 9 + 32

def make_message(temp):

print("make_message”)

return "The temperature is " + str(temp)

for tempc in [-40, 0, 37]:

tempf = c_to_f(tempc)

message = make_message(tempf)

print(message)

c_to_f

make_message

The temperature is -40.0

c_to_f

make_message

The temperature is 32.0

c_to_f

make_message

The temperature is 98.6

34

35 of 44

Each variable should represent one thing

def atm_to_mbar(pressure):

return pressure * 1013.25

def mbar_to_mmHg(pressure):

return pressure * 0.75006

# Confusing

pressure = 1.2 # in atmospheres

pressure = atm_to_mbar(pressure)

pressure = mbar_to_mmHg(pressure)

print(pressure)

# Better

in_atm = 1.2

in_mbar = atm_to_mbar(in_atm)

in_mmHg = mbar_to_mmHg(in_mbar)

print(in_mmHg)

# Best

def atm_to_mmHg(pressure):

in_mbar = atm_to_mbar(pressure)

in_mmHg = mbar_to_mmHg(in_mbar)

return in_mmHg

print(atm_to_mmHg(1.2))

Corollary: Each variable should contain values of only one type

# Legal, but confusing: don’t do this!

x = 3

x = "hello"

x = [3, 1, 4, 1, 5]

35

36 of 44

Review: how to evaluate a function call

  1. Evaluate the function and its arguments to values
  2. Create a new stack frame
    • The parent frame is the one where the function is defined
      • In CSE 160, this is always the global frame
    • A frame has bindings from variables to values
    • Looking up a variable starts in the local frame
      • Proceeds to its parent frame (the global frame) if no match in local frame
      • All the frames together are called the “environment”
  3. Assign the actual argument values to the formal parameter variable
    • Add these as bindings in the new stack frame
  4. Evaluate the body
    • Execute the statements in the function body
    • At a return statement, return the value and exit the function
    • If reach the end of the body of the function without encountering �a return statement, then return the value None �(It is also fine to explicitly have a statement: return None )
  5. Remove the stack frame
  6. The call evaluates to the returned value

36

37 of 44

HW2 Questions

  • Can I change any of the code you give me in the dna_analysis.py file?
  • Can I use the triangle button to run dna_analysis.py?
  • Do I need to understand what the code inside the function filename_to_string is doing?
  • Can I do the problems in HW2 in any order?
  • Is HW2 just about writing more Python code?

37

38 of 44

Bonus Slides: �Extra Function Calls

38

39 of 44

Example of function invocation

def square(x):

return x * x

Variables:

square(3) + square(4) (none)

return x * x x: 3

return 3 * x x: 3

return 3 * 3 x: 3

return 9 x: 3

9 + square(4) (none)

return x * x x: 4

return 4 * x x: 4

return 4 * 4 x: 4

return 16 x: 4

9 + 16 (none)

25 (none)

39

40 of 44

Expression with nested function invocations:�Only one executes at a time

def fahr_to_cent(fahr):

return (fahr – 32) / 9.0 * 5

def cent_to_fahr(cent):

return cent / 5.0 * 9 + 32

Variables:

fahr_to_cent(cent_to_fahr(20)) (none)

return cent / 5.0 * 9 + 32 cent: 20

return 20 / 5.0 * 9 + 32 cent: 20

return 68 cent: 20

fahr_to_cent(68) (none)

return (fahr – 32) / 9.0 * 5 fahr: 68

return (68 – 32) / 9.0 * 5 fahr: 68

return 20 fahr: 68

20 (none)

40

41 of 44

Expression with nested function invocations:�Only one executes at a time

def square(x):

return x * x

Variables:

square(square(3)) (none)

return x * x x: 3

return 3 * x x: 3

return 3 * 3 x: 3

return 9 x: 3

square(9) (none)

return x * x x: 9

return 9 * x x: 9

return 9 * 9 x: 9

return 81 x: 9

81 (none)

41

42 of 44

Function that invokes another function:�Both function invocations are active

def square(z):

return z * z

def hypotenuse(x, y):

return math.sqrt(square(x) + square(y))

Variables:

hypotenuse(3, 4) (none)

return math.sqrt(square(x) + square(y)) x: 3 y:4

return math.sqrt(square(3) + square(y)) x: 3 y:4

return z * z z: 3 x: 3 y:4

return 3 * 3 z: 3 x: 3 y:4

return 9 z: 3 x: 3 y:4

return math.sqrt(9 + square(y)) x: 3 y:4

return math.sqrt(9 + square(4)) x: 3 y:4

return z * z z: 4 x: 3 y:4

return 4 * 4 z: 4 x: 3 y:4

return 16 z: 4 x: 3 y:4

return math.sqrt(9 + 16) x: 3 y:4

return math.sqrt(25) x: 3 y:4

return 5 x: 3 y:4

5 (none)

42

43 of 44

Shadowing of formal variable names

def square(x):

return x * x

def hypotenuse(x, y):

return math.sqrt(square(x) + square(y))

Variables:

hypotenuse(3, 4) (none)

return math.sqrt(square(x) + square(y)) x: 3 y:4

return math.sqrt(square(3) + square(y)) x: 3 y:4

return x * x x: 3 x: 3 y:4

return 3 * 3 x: 3 x: 3 y:4

return 9 x: 3 x: 3 y:4

return math.sqrt(9 + square(y)) x: 3 y:4

return math.sqrt(9 + square(4)) x: 3 y:4

return x * x x: 4 x: 3 y:4

return 4 * 4 x: 4 x: 3 y:4

return 16 x: 4 x: 3 y:4

return math.sqrt(9 + 16) x: 3 y:4

return math.sqrt(25) x: 3 y:4

return 5 x: 3 y:4

5 (none)

43

Same formal parameter name

Same formal parameter name, �but two completely different variables

Formal parameter is a new variable

44 of 44

Shadowing of formal variable names

def square(x):

return x * x

def hypotenuse(x, y):

return math.sqrt(square(x) + square(y))

Variables:

hypotenuse(3, 4) (none) hypotenuse()

return math.sqrt(square(x) + square(y)) x: 3 y:4

return math.sqrt(square(3) + square(y)) square() x: 3 y:4

return x * x x: 3 x: 3 y:4

return 3 * 3 x: 3 x: 3 y:4

return 9 x: 3 x: 3 y:4

return math.sqrt(9 + square(y)) x: 3 y:4

return math.sqrt(9 + square(4)) square() x: 3 y:4

return x * x x: 4 x: 3 y:4

return 4 * 4 x: 4 x: 3 y:4

return 16 x: 4 x: 3 y:4

return math.sqrt(9 + 16) x: 3 y:4

return math.sqrt(25) x: 3 y:4

return 5 x: 3 y:4

5 (none)

44

Same diagram, with variable scopes or environment frames shown explicitly