CS30 - Spring 2016 - Class 7

Example code in this lecture

   number_game.py
   scope.py

Lecture notes

  • Administrative
       - Assignment 1 grading issue
          - if you lost points for "print" on the welcome function, e-mail me
       - Assignment 3

  • run number_guessing_game in number_game.py code
       - 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

  • 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

  • look at number_guessing_game function in number_game.py code
       - 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 had "while stillguessing:"
       - 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

  • references
       - objects reside in memory
          - anytime we create a new int, float, string, list, etc. it is allocated in memory

       - variables are references to objects in memory
          - a variable does NOT hold the value itself, but it is a references to where that value resides
          - arm/hand metaphor
             - think of variables like your hands
             - you can point them at things and then ask questions:
                - what is the name of the thing that my right hand points to?
                - for the thing that my left hand points to, do something?
             - you can point them to new things (i.e. assignment)
             - you can point point them at the same thing (i.e. assignment one to the other)
             - but they are separate things!
                - similarly, variables are separate things
                - they can reference the same thing, but they are not the same thing
                
       - when you ask for the value of a variable, it's actually getting the value of the things it references in memory

  • = (i.e. assignment)
       - when we say x = y what we're actually saying is let x reference the same thing that y references
       - x and y are still separate variables
          - if we say x = z that does NOT change the value of y

  • mutable objects
       - if an object is mutable (i.e. it has methods that change the object) then we have to be careful when we have multiple things referencing the same object (this is called "aliasing")

          >>> x = [1, 2, 3, 4, 5]
          >>> y = x

          - what does this look like in memory?
             - there is one list object
             - both x and y are references to that same object (drawn as arrows in class)
          
          - what happens when I call a method that changes/mutates the object?

             >>> y.reverse()
             >>> y
             [5, 4, 3, 2, 1]
             >>> x
             [5, 4, 3, 2, 1]

             x and y are references to the same object!

             >>> x[0] = 0
             >>> x
             [0, 4, 3, 2, 1]
             >>> y
             [0, 4, 3, 2, 1]

             statements that mutate the object that are done on either variable will affect this object

             >>> y[0] = 15
             >>> x
             [15, 4, 3, 2, 1]
             >>> y
             [15, 4, 3, 2, 1]

       - If we change what one of them references using assignment, then it will NOT affect the original object
          >>> y = [0] * 5
          >>> y
          [0, 0, 0, 0, 0]
          >>> x
          [0, 4, 3, 2, 1]

          there are now two separate objects and x and y each reference a different object

       - why hasn't this problem come up with ints/floats/bools/strings? We said they're objects?
          >>> x = 10
          >>> y = x

          - what does the memory picture look like?
             - we just have one int object!
             - x and y are both references to that one int object
          - it seems like we could have the same problem as with lists...
          - ints/floats/bools/strings are not mutable!
             - there is no way for us to change the underlying object
                - therefore, even though two variables reference the same int, it's as if they referenced separate ints
          - draw the memory picture for the following:

             >>> x = 10
             >>> y = x
             >>> y = 15
             >>> x
             10
             >>> y
             15

             - we now have 2 objects (10 and 15)
             - just like with lists, changing what y references does not affect x


  • parameter passing and references
       - some terminology:
          - when we define a functions 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
       
       - 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?

          def changer(a):
             a[0] = 100

          >>> 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
          - 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
       - notice, however, that operations that do not change/mutate the object will NOT be seen outside the function
          def no_changer(a):
             a = [0]*5

          >>> 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

  • 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?
       - performance (it takes work to copy the object)
          - often we just want the value and don't need to mutate the underlying object

  • The "scope" of a variable is the portion of the code we can reference that variable in and have it be valid
       - 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
       - 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

       - What would be printed out if we ran scope.py code?
          - 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 variablem x to -1
          - After mystery1 returns we print out the values of the global variables x, y and z
             - y and z are unchanged
                - the global variable z was NOT changed mystery1, only the parameter z
             - x *was* changed
             

       - Why do programming languages limit a variable's scope?