Class Notes: Variables and Functions

**Variables****Statements and Expressions****Functions****Built-in Functions****Module Functions****Variable Scope****Return Statements****Print versus Return****Function Composition****Helper Functions****Recommended Functions****Test Functions**

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

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

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

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

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

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

**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**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**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))**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!

- A broken test function