Variables and Functions


  1. Variables
  2. Statements and Expressions
  3. Functions
  4. Built-in Functions
  5. Module Functions
  6. Variable Scope
  7. Return Statements
  8. Print versus Return
  9. Function Composition
  10. Helper Functions
  11. Recommended Functions
  12. Test Functions

  1. Variables
    • A variable is a named value that references or stores a piece of data.
      # we put a value in a variable using an = sign
      x = 5
      print(x) # x evaluates to 5
      print(x*2) # evaluates to 10

    • Unlike in math, variables can have new values assigned to them, even values of different types.
      y = 10
      print(y - 2)
      
      y = True
      print(y)

    • Variables can be given any name, as long as it starts with a letter and contains no special characters.
      numberOfRabbits = 40
      courseIs15112 = True
      99problems = 0 # will crash because it starts with a number

    • Variables can be updated with assignment operations.
      x = 5
      x += 2 # same as x = x + 2
      print(x) # should be 7
      
      # This can be done with any of the arithmetic operations.
      y = 350
      y //= 10
      print(y) # should be 35

  2. Statements and Expressions
    • An expression is a data value or an operation that evaluates to a value.
      # Examples of expressions.
      # Note that when this is run in the editor, none of these values are displayed.
      4
      "Hello World"
      7 + 2
      True or False
      (2 < 3) and (9 > 0)

    • A statement is a line of code that performs an action. Unlike expressions, statements cannot be used in operations.
      # Examples of statements.
      print(4)
      x = True

  3. Functions
    • A function is a procedure (a sequence of statements) stored under a name that can be used repeatedly by calling the name.
      # A function is composed of two parts: the header and the body.
      
      # The header defines the name and parameters.
      # A function header is written as follows: def functionName(parameters):
      # The parameters are variables that will be provided when the function is called.
      # The header ends with a colon to indicate that a body will follow.
      
      # The body contains the actions (statements) that the function performs.
      # The body is written under the function with an indent.
      # When the lines are no longer indented, the function body ends.
      # Functions usually contain a return statement. This will provide the result when the function is called.
      
      # Example:
      
      def double(x):
          print("I'm in the double function!")
          return 2 * x
      
      # To call the function, we use the function's name,
      # followed by parentheses which contain the data values we want to use, called function arguments.
      # This function call will evaluate to an expression.
      
      print(double(2)) # will print 4
      print(double(5)) # will print 10
      print(double(1) + 3) # will print 5

    • Functions can have as many parameters as they need, or none at all.
      def f(x, y, z):
          return x + y + z
      
      print(f(1, 3, 2)) # returns 6
      
      def g():
          return 42
      
      print(g()) # returns 42
      
      # Note - the number of arguments provided must match the number of parameters!
      print(g(2)) # will crash
      print(f(1, 2)) # would also crash if it ran

  4. Builtin Functions  
    # Some functions are already provided by Python
    
    print("Type conversion functions:")
    print(bool(0))   # convert to boolean (True or False)
    print(float(42)) # convert to a floating point number
    print(int(2.8))  # convert to an integer (int)
    
    print("And some basic math functions:")
    print(abs(-5))   # absolute value
    print(max(2,3))  # return the max value
    print(min(2,3))  # return the min value
    print(pow(2,3))  # raise to the given power (pow(x,y) == x**y)
    print(round(2.354, 1)) # round with the given number of digits

  5. Module Functions  
    Python has many different functions already implemented, but not available immediately. To use these functions, you must import a module. You can find these modules by reading the Python documentation online.

    Call without importing
    print(math.factorial(20))  # we did not first import the math module
    
    # Python output:
    #   NameError: name 'math' is not defined

    Call with importing
    import math
    print(math.factorial(20))  # much better...
    
    # Note that the module name is included before the function name, separated by a .

  6. Variable Scope  
    • Variables exist in a specific scope based on when they are defined. This means they are not visible and cannot be used outside that scope, in other parts of the code.
      def f(x):
          print("x:", x)
          y = 5
          print("y:", y)
          return x + y
      print(f(4))
      print(x) # will crash!
      print(y) # would also crash if we reached it!

    • Variables in functions have a local scope. They exist only inside the immediate function, and have no relation to variables of the same name in different functions.
      def f(x):
          print("In f, x =", x)
          x += 5
          return x
      
      def g(x):
          y = f(x*2)
          print("In g, x =", x)
          z = f(x*3)
          print("In g, x =", x)
          return y + z
      
      print(g(2))
      
      # Another example
      
      def f(x):
          print("In f, x =", x)
          x += 7
          return round(x / 3)
      
      def g(x):
          x *= 10
          return 2 * f(x)
      
      def h(x):
          x += 3
          return f(x+4) + g(x)
      
      print(h(f(1)))

    • When defined outside of functions, variables have a global scope and can be used anywhere.
      # In general, you should avoid using global variables.
      # You will even lose style points if you use them!
      # Still, you need to understand how they work, since others
      # will use them, and there may also be some very few occasions
      # where you should use them, too!
      
      g = 100
      
      def f(x):
          return x + g
      
      print(f(5)) # 105
      print(f(6)) # 106
      print(g)    # 100
      
      # Another example
      
      def f(x):
          # If we modify a global variable, we must declare it as global.
          # Otherwise, Python will assume it is a local variable.
          global g
          g += 1
          return x + g
      
      print(f(5)) # 106
      print(f(6)) # 108
      print(g)    # 102

  7. Return Statements  
    • Basic Example
      def isPositive(x):
          return (x > 0)
      
      print(isPositive(5))  # True
      print(isPositive(-5)) # False
      print(isPositive(0))  # False

    • Return ends the function immediately:
      def isPositive(x):
          print("Hello!")   # runs
          return (x > 0)
          print("Goodbye!") # does not run ("dead code")
      
      print(isPositive(5))  # prints Hello, then True

    • No return statement --> return None:
      def f(x):
          x + 42
      
      print(f(5)) # None

    • Another example:
      def f(x):
          result = x + 42
      
      print(f(5)) # None

  8. Print versus Return  
    • Confusing print and return is a common early mistake.
      def cubed(x):
          print(x**3) # Here is the error!
      
      cubed(2)          # seems to work!
      print(cubed(3))   # sort of works (but prints None, which is weird)
      print(2*cubed(4)) # Error!

    • Once again (correctly):
      def cubed(x):
          return (x**3) # That's better!
      
      cubed(2)          # seems to be ignored (why?)
      print(cubed(3))   # works!
      print(2*cubed(4)) # works!

  9. Function Composition  
    def f(w):
        return 10*w
    
    def g(x, y):
        return f(3*x) + y
    
    def h(z):
        return f(g(z, f(z+1)))
    
    print(h(1)) # hint: try the "visualize" feature

  10. Helper Functions  
    # We commonly write functions to solve problems.
    # We can also write functions to store an action that is used multiple times!
    # These are called helper functions.
    
    def onesDigit(n):
        return n%10
    
    def largerOnesDigit(x, y):
        return max(onesDigit(x), onesDigit(y))
    
    print(largerOnesDigit(134, 672)) # 4
    print(largerOnesDigit(132, 674)) # Still 4

  11. Recommended Functions
    # There are a few functions from modules you'll definitely want to use in the assignments
    
    # First: the built-in round function has confusing behavior when rounding 0.5.
    # Use our function roundHalfUp to fix this.
    
    def roundHalfUp(d):
        # Round to nearest with ties going away from zero.
        # You do not need to understand how this function works.
        import decimal
        rounding = decimal.ROUND_HALF_UP
        return int(decimal.Decimal(d).to_integral_value(rounding=rounding))
    
    print(round(0.5)) # This evaluates to 0 - what!
    print(round(1.5)) # And this will be 2 - so confusing!
    print(roundHalfUp(0.5)) # Now this will always round 0.5 up (to 1)
    print(roundHalfUp(1.5)) # This still rounds up too!
    
    # Second: when comparing floats, == doesn't work quite right.
    # Use almostEqual to compare floats instead
    
    print(0.1 + 0.1 == 0.2) # True, but...
    d1 = 0.1 + 0.1 + 0.1
    d2 = 0.3
    print(d1 == d2) # False!
    print(d1)       # prints 0.30000000000000004 (uh oh)
    print(d1 - d2)  # prints 5.55111512313e-17 (tiny, but non-zero!)
    # Moral: never use == with floats!
    
    # Python includes a builtin function math.isclose(), but that function
    # has some confusing behavior when comparing values close to 0.
    # Instead, let's just make our own version of isclose:
    
    def almostEqual(x, y):
        return abs(x - y) < 10**-9
    
    # This will now work properly!
    print(almostEqual(0, 0.0000000000001))
    print(almostEqual(d1, d2))

  12. Test Functions  
    • A broken test function
      def onesDigit(n):
          return n%10
      
      def testOnesDigit():
          print("Testing onesDigit()...", end="")
          assert(onesDigit(5) == 5)
          assert(onesDigit(123) == 3)
          assert(onesDigit(100) == 0)
          assert(onesDigit(999) == 9)
          print("Passed!")
      
      testOnesDigit() # Passed!  Why is this bad?

    • A better version
      def onesDigit(n):
          return n%10
      
      def testOnesDigit():
          print("Testing onesDigit()...", end="")
          assert(onesDigit(5) == 5)
          assert(onesDigit(123) == 3)
          assert(onesDigit(100) == 0)
          assert(onesDigit(999) == 9)
          assert(onesDigit(-123) == 3) # Added this test
          print("Passed!")
      
      testOnesDigit() # Crashed!  So the test function worked!