Functions¶

Consider the expression 2 + 2: whatever line of code it appears on, we evaluate it in its entirety before using it.

For example, x = 2 + 2 stores the result of the 2 + 2 expression, rather than the expression itself. Thus, x now contains the value $4$.

What if we wanted to do something more complicated than a simple arithmetic equation?

Let's take as an example a simple arithmetic equation: $2x + 1$. In Math, we might want to reference this equation elsewhere by saying that we should consider $f(x)$ to be that formula. Thus, everwhere we see $f(x)$, it's actually $2x + 1$. This allows us to plug in arbitrary values and build up results:

$f(x) = 2x + 1\\\text{let }x = 4\\f(4) = 9$

We can do the same thing in programming, using "functions".

In [ ]:
import math
In [ ]:
len("hello")
In [ ]:
len("")
In [ ]:
math.sqrt(9)
In [ ]:
math.sqrt(7)
In [ ]:
list(range(1, 5))
In [ ]:
list(range(8))
In [ ]:
math.sin(0)
In [ ]:
str(17)
In [ ]:
import random
random.random()

Function call examples:

In [ ]:
x = 8
y = 16
z = math.sqrt(16)
u = math.sqrt(y)
v = math.sqrt(8 + 8)
w = math.sqrt(x + x)
In [ ]:
w = math.sqrt(x + x)
In [ ]:
w

Defining a new function¶

Instead of $f(x) = 2x + 1$, we're going to define a new body of code, and give it the name dbl_plus:

In [ ]:
def dbl_plus(x):
    return 2 * x + 1

We can then use (or "call") that code thusly:

In [ ]:
dbl_plus(2)
In [ ]:
x = 2 + 2
x
In [ ]:
dbl_plus(3)
In [ ]:
 
In [ ]:
x = 346
dbl_plus(x)

Practice Reading¶

In [ ]:
def multiply(x, y):
    print("x,y:", x, y)
    z = 0
    return x * y

x = 2
y = 1
result = multiply(3, 4)
print(result)
In [ ]:
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?

More Definition Examples¶

In [ ]:
def dbl_plus(x):
    return 2 * x + 1
In [ ]:
def instructor_name():
    return "Alessia Fitz Gibbon"
In [ ]:
def square(x):
    return x * x
In [ ]:
def calc_grade(points):
    grade = points * 10
    return grade

Evaluating a Function Call¶

  1. Evaluate the function name and its parameters
In [42]:
x = 3
y = 4

x * y
3 * y
3 * 4
12
Out[42]:
12
In [ ]:
def square(n):
    return n ** 2
In [44]:
square
Out[44]:
<function __main__.square(n)>
In [ ]:
x = 3
y = 4

z = square(x * y)
  • square evaluated to ensure it is a function (that the open parentheses ( is going to be syntactically correct).
  • The x * y expression and sub-expressions are evaluated. x becomes 3, y becomes 4, then the whole x * y becomes 12.
In [ ]:
# As such, we're essentially calling:
z = square(3 * 4)
z = square(12)
  1. Python creates a new frame dedicated to this function.
    • Aside: a "frame" is a container internal to Python that contains the bindings between variables and values applicable to that scope.

  1. Assign the "actual parameters" (the values evaluated in step 1) to the variables named in the function's definition (the "formal" parameters)
    • In this example, 3 * 4 -> 12 -> assigned to square's x

The square frame having an x set to 12 would be equivalent to doing

 def square():
     x = 12
     ...

But then we'd have no way to specify arbitrary values for square's x!

  1. Next, evaluate and run the entire body of the function. Every line of the function is run as normal until we reach a return statement.
  • The return statement says to evaluate the given expression, and copy the value out to the original call site.
In [ ]:
# Without functions, this would be the equivalent of:
x = 3
y = 4
x = x * y
z = x * x
  1. Once the return expression's value is copied back to the call site, we can remove the square stack frame and continue with the rest of the program.