Lecture 7: Functions in Depth, Debugging
Topics
Lingering HW3 Questions
Presentation
How do functions really work?
- Refresher on arrow diagrams.
- Let's go back to simple.py.
We can walk through what happens when we make a call to
add_then_double
, e.g.>>> add_then_double(10, 20)
- The arguments to the function (10 and 20) are evaluated
- they could have been more complicated expressions, e.g.
add_then_double(5+5, 40/2)
- they could have been more complicated expressions, e.g.
- The function is then called and the parameters are associated with the values that are passed in
think of it like saying:
a = 10 b = 20
- we now have two variables in the function that have the values of the information passed in
- the only tricky thing is that these variables are unique to this function, so even if they're defined globally, they will be different than the global variables
- The function body for
add_then_double
is executed a statement at a time- This will in turn call
add(a, b)
- like any other function call we:
- evaluate the arguments, in this case, we evaluated
a
, which gives us 10, andb
, which gives us 20 - we then pass these values to
add
- these values get associated with parameter names,
num1
andnum2
- these values get associated with parameter names,
- evaluate the arguments, in this case, we evaluated
add
is only one line of code, it adds the number and then returns that valuenum1 + num2
gives us 30(10 + 20)
- the
return
statement means wherever this function was called, that is the value that call should evaluate to- the call
add(a, b)
will then represent the value 30
- the call
- like any other function call we:
- We then assign what
add(a,b)
returns to the variableadded
double
is called with what was in the variableadded
(30) and gives us back 60- 60 is then associated with the variable
doubled
- Finally,
add_then_double
returnsdoubled
, which has the value 60
- This will in turn call
- The arguments to the function (10 and 20) are evaluated
- If we called
add_then_double
from the console, we will see the value 60 echoed, not because it was printed out but because that call (add_then_double(10, 20)
) has been evaluated to the value 60 We could just have easily done other things with it, e.g.
>>> add_then_double(10,20) * 2 120 >>> result = add_then_double(10,20) >>> result 60 >>> add_then_double(add_then_double(10, 20), 4) 128
write a method called
contains
that takes a list and an item and returns True if the item is in the list, False otherwise, i.e.def contains(some_list, value): # ...
- Look at the first option in contains.py
- loops through each value in the list
- if it finds one that is equal to the item we're looking for, then return True
- remember that
return
immediately exits the function - this will stop the loop and then give back True from the function
- remember that
- if we make it through the entire list without finding one that's equal (and therefore exiting the function) then the loop will finish and we'll return False
- this is similar to the
isprime
function we saw before in while.py
- this is similar to the
- if it finds one that is equal to the item we're looking for, then return True
- loops through each value in the list
- the
contains_alternative
function in contains.py code is another way of accomplishing this- we use a variable to keep track of whether or not we've found the item
- initially, we set it to False
- we then loop through all of the values
- if we find the item we're looking for, we set the variable to True
- not that this could happen multiple times if the item occurs multiple times, but it's fine to set it to True again if it's already set
- after the loop finishes, we return found
- if we didn't find any, it will still be False
- otherwise, it will have been set to True
- we use a variable to keep track of whether or not we've found the item
- Look at the first option in contains.py
parameter passing and references
- some terminology:
when we define a function's parameters we are defining the "formal parameters"
def my_function(a, b): return a+b
- a and b are formal parameters
- until the function is actually called, they don't represent actual values
when we call a function, the values that we give to the function are called the "actual parameters" (sometimes also called the arguments)
>>> x = 10 >>> y = 20 >>> my_function(x, y) 30
- the values 10 and 20 are the actual parameters (or similarly, x and y become the actual parameters)
- when a function is called the following happens:
- the values of the actual parameters are determined (we evaluate the expression representing each parameter)
- the value is just an object (could be an int, float, string, etc)
- these values are then "bound" or assigned to the formal parameters
- it's very similar to assignment
- the formal parameters represent new variables
- the formal parameters will then REFERENCE the same objects as those passed in through the actual parameters
- the values of the actual parameters are determined (we evaluate the expression representing each parameter)
- let's consider the picture for our function above
- x and y are both references to int objects
- when we call my_function, the formal parameters a and b, will represent two new variables
- these variables will reference the same thing as their actual parameters
think of it like running the statements:
a = x b = y
- a will reference the same thing as x
- b will reference the same thing as y
- for non-mutable objects, this whole story doesn't really matter. Why?
- they could be references to the same thing or copies, if we can't mutate the object, the behavior is the same
- how does this change for mutable objects?
look at the changer function in parameter_passing.py code
>>> x = [1, 2, 3, 4, 5] >>> changer(x)
- what does the picture look like for this function call?
- x was a variable that references a list object
- when the function was called, the formal parameter a will represent a new variable
- a will reference the same thing as x
- think of it like running the statement
a = x
- think of it like running the statement
- because the object is mutable and since the formal parameter references the same object as what was passed in, changes made to the object referenced by a will also be seen in x
- what does the picture look like for this function call?
- notice, however, that operations that do not change/mutate the object will NOT be seen outside the function
look at the no_changer function in parameter_passing.py
>>> x = [1, 2, 3, 4, 5] >>> no_changer(x)
- in this case, we're assigning a to some new object
- we can't change what x references!
- x and a will no longer reference the same object
- any changes to a after this will not affect x
- in this case, we're assigning a to some new object
- why is variable assignment and parameter passing done based on the references (i.e. a shallow copy) rather than a deep copy of the whole object?
- sometimes you want to modify the object passed in
- performance (it takes work to copy the object)
- often we just want the value and don't need to mutate the underlying object
- in some programming languages (Rust, C, C++) your parameters annotate whether they are by reference or by value
- in some programming languages (Rust) your parameters annotate whether the function is allowed to mutate objects passed in by reference
bool variables
- just like any other variables except it's of type bool
- we've used variables to store ints, floats and strings
- this works the same way
for example
>>> x = True >>> x True >>> x = 10 < 0 >>> x False
- we need some way of keeping track whether or not the user has guessed correctly or not
- we can us a bool variable and initially set it to some value
- condition the while loop on this variable
- change the value when we get it correct
in
(we wrotecontains
just before!)python actually has the
contains
functionality built-in>>> 1 in [1, 2, 3] True >>> 2 in [1, 2, 3] True >>> 5 in [1, 2, 3] False >>> "banana" in [1, 2, 3] False
the item has to be in the list as one of the individual items, e.g. the following doesn't work:
>>> [1, 2] in [1, 2, 3] False but this would: >>> [1, 2] in [[1, 2], 2, 3] True
in works for other sequences like strings (does a character exist in a string) or tuples:
>>> "a" in "banana" True
For strings, you can actually ask if a substring is in a string
>>> "ana" in "banana" True
- run number_guessing_game in number_game.py
- picks a random number between 1 and 20 and you try and guess it
- keeps prompting you until you get it right
- gives you hints as to whether you need to guess higher or lower
- how could we implement this?
- pick a random number
- as long as (while) the user hasn't guessed the right answer
- get the guess from the user
- if it's the right answer
- print out "Good job!"
- somehow indicate that we're done looping
- otherwise, if the guess is too low
- print out higher
- otherwise (i.e. the guess must be too high)
- print out lower
- picks a random number between 1 and 20 and you try and guess it
- look at
number_guessing_game
function in number_game.py- use a variable called
finished
and initially set it to False- could have also use a variable like
stillguessing
and set it to True and then hadwhile stillguessing:
- could have also use a variable like
- when they get the number right we set
finished = True
and we will therefore exit the loop - notice there are other ways of writing this function, e.g.
number_guessing_game2
- use a variable called
A few more sequence operators
We've seen + for appending strings
>>> "banana" + " split" "banana split"
it also works on other sequences
>>> [1, 2, 3] + [4, 5] [1, 2, 3, 4, 5] >>> (1, 2, 3) + (4, 5) (1, 2, 3, 4, 5)
What do you think times does?
>>> "*" * 10 '**********' >>> "I'm " + "very "* 4 + "excited" "I'm very very very very excited" >>> [0] * 10 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] >>> [1, 2, 3] * 2 [1, 2, 3, 1, 2, 3]
scope.py
- When we run the file, the first two functions will get defined
- Then, it will execute the three statements at the end of the file
- The first print statement will print out 20
- The second print statement will print out 30
- Why not 60? I.e., in
double_input
, we assigned 20 to val as the first line.val = 2*val
updates the value of the parameter NOT the variable outside the function
- Why not 60? I.e., in
- The "scope" of a variable is the portion of the code we can reference that variable in and have it be valid
- Scope diagrams
When we declare a variable in the interpreter, what is its scope?
>>> x = 10
- the scope is any shell statements/expressions typed after it
- When we declare a variable (outside a function) in a file, what is its scope?
- anywhere below it in the file
- remember, running a program is very similar to typing those commands into the shell
- anywhere below it in the file
- When we declare a variable inside a function, what is its scope?
- anywhere below it in the FUNCTION
- What is the scope of the parameters?
- anywhere in the FUNCTION
- it doesn't really make sense outside of the function since we wouldn't have a value for it
- Additionally, the scope also defines the context for a variable reference.
- What would be printed out if we ran scope2.py?
- the program starts at the top and declares three "global" variables x, y and z
- then it declares two functions
- then it calls
mystery1
with 10 and 20- the parameter a gets the value 10
- the parameter z gets the value 20
- notice that this is different than the global variable z!
- in particular, when we execute
z = 100
, this reassigns the value of the local z, but not the global
- The program prints out s, x, y and z
- s is a local variable
- x and y are global
- z is the parameter
- and then sets the value of the global variable z to 100
- After
mystery1
returns we print out the values of the global variables x, y and z- all three are unchanged!
- x and y weren't changed anywhere
- z was changed in
mystery1
, but the z being changed was the parameter, not the global z
- all three are unchanged!
- What would happen if we added a call to
mystery2
at the end and ran scope2.py?- Error. The scope of a is only defined within
mystery1
, so it cannot be accessed anywhere outside the function.
- Error. The scope of a is only defined within
- the program starts at the top and declares three "global" variables x, y and z
- Why do programming languages limit a variable's scope?