CS30 - Spring 2015 - Class 7

Example code in this lecture

   number_game.py
   scope.py

Lecture notes

  • Administrative
       - Assignment 3

  • other values besides True/False can be interpreted in a bool context
       if "banana":
          print "I'm True!"
       else:
          print "I'm False!"

       - Besides True/False, most values can also be used in a boolean context
          - 0, 0.0 and "" are all equivalent to False
          - Everything else is True

       - Be careful, though!
          >> True or 10
          True
          >>> False and 10
          False
          >>> 10 or False
          10
          >>> False or 10
          10

       - Why did it do this?
          - Two things are going on here:
             1) Python uses "short circuit evaluation" for boolean expressions: it only evaluates the second expression if it NEEDS to
                - True or 10: evaluates True, sees or and doesn't need to evaluate 10
                - False and 10: evaluates false, see and and doesn't need to evaluate 10
             2) Python returns the last value of the last thing it evaluated before when it finally decides what the value of the expression is
                - 10 or False: When we see 10, it is interpreted as a "True" value and since it is followed by or, we can immediately stop
                   - Notice the difference here:

                   >>> 10 and True
                   True
                - False or 10: We have to check the 10 and only then do we see that the expression is true

       - A few more examples:
          >>> "" or False
          False
          >>> False or ""
          ''
       
       - Bottom line, generally not good to mix non-booleans and booleans

  • 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 it 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 values 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?