Many times in this class we've used code already written by others (e.g., if you want to convert "42" to 42) -- and so we've benefited from encapsulation, whereby useful code is grouped togeher and can be used in multiple places for multiple purposes.
Now time to make our own! To get us there, let's solve a problem :)
grades1 = input("Enter first list of grades: ").split()
grades2 = input("Enter second list of grades: ").split()
sum1 = 0
for grade in grades1:
sum1 += float(grade)
avg1 = sum1/len(grades1)
sum2 = 0
for grade in grades2:
sum2 += float(grade)
avg2 = sum2/len(grades2)
print("Averages: first={:.2f}, second={:.2f}".format(avg1, avg2))
if avg1 > avg2:
print("First wins!")
elif avg2 > avg1:
print("Second wins!")
else:
print("Tie!")
In this simple example there were things we did two times (one for each grade list):
Proposal: let's turn these into functions! (after some instructional detours)
All functions have...
# No parameters/returns (not very common)
def say_hi():
print("hi")
for _ in range(5):
say_hi()
# if you try to capture what is returned...
# you get a special data type, None
x = say_hi()
print(x)
# 1 parameter (n), no returns (not very common)
def say_hi_n_times(n):
for _ in range(n):
print("hi")
# 2 gets copied into n for this execution of say_hi_n_times
say_hi_n_times(2)
print()
# 3 gets copied into n for this execution of say_hi_n_times
say_hi_n_times(3)
# 2 parameters (txt, n), no returns (not very common)
def say_something_n_times(txt, n):
for _ in range(n):
print(txt)
# "hi" gets copied into txt, 2 gets copied into n
# (since "hi" is first and txt is first, ...)
say_something_n_times("hi", 2)
print()
# "bye" gets copied into txt, 3 gets copied into n
say_something_n_times("bye", 3)
# no parameters, 1 return (not very common)
def word_of_day():
# whenever we get to a return,
# the function exits and the
# call becomes whatever is below
return "delay"
# imagine this turns into x = "delay"
x = word_of_day()
print(x)
print(word_of_day())
# no parameters, 2 returns (not very common)
def word_and_number_of_day():
# returns a value of type "tuple":
# basically a read-only list
return "delay", 7
# Method 1: individual variables
word, num = word_and_number_of_day()
print("{} {}".format(word, num))
# Method 2: single variable
wn_tuple = word_and_number_of_day()
print("{} {}".format(wn_tuple[0], wn_tuple[1]))
# 1 parameter, 1 return
def fortune(day_of_week):
if day_of_week == "Friday":
return "Woohoo!"
else:
return "Is it Friday yet?"
# never executed, why?
print("I'm so lonely :(")
print(fortune("Sunday"))
print(fortune("Monday"))
print(fortune("Tuesday"))
print(fortune("Wednesday"))
print(fortune("Thursday"))
print(fortune("Friday"))
print(fortune("Saturday"))
# multiple parameters, multiple returns
def maths(a, b):
return a+b, a*b
add_result, mult_result = maths(2, 4)
print("{} {}".format(add_result, mult_result))
# Functions can call each other!
def twice_length(s):
return 2 * len(s)
def rabbit_string(s):
return s * twice_length(s)
print(rabbit_string("ha!"))
print(rabbit_string("OHNO!"))
# A function can itself be supplied as a parameter!
def add3(n):
return n + 3
def mult2(n):
return n * 2
def do_to_all(func, mylist):
result = []
for element in mylist:
result.append(func(element))
return result
print(do_to_all(add3, [1, 2, 3]))
print(do_to_all(mult2, ['a', 'b', 'c']))
All variables have a scope, which is the code in which they "live" (i.e. where they can be used).
A variable created outside a function has global scope (it can be accessed anywhere).
A variable created inside a function, including a parameter, has local scope (it only lives within the function).
def func1(a):
# both a and b override any global a/b,
# but gone as soon as the function ends
b = 10
return a + b
a = 1
b = 2
c = func1(5)
print("a={}, b={}, c={}".format(a, b, c))
A function can access/modify global data, but in general this should be avoided (it can make code hard to understand, debug). A common exception is using a "constant" -- a variable whose value is unchanging (commonly named in caps to indicate it's a constant).
KM_TO_MILES = 0.621371
def convert_to_miles(km):
return KM_TO_MILES * km
km = 5
print("{} km ~ {:.2f} miles".format(km, convert_to_miles(km)))
# Convert a list of strings to a list of numbers
# Looks pretty similar to something above...
def float_list_from_scratch(mylist):
result = []
for num_str in mylist:
result.append(float(num_str))
return result
# Better :)
def float_list(mylist):
return do_to_all(float, mylist)
# Sum up a list of numbers
def sum_list(mylist):
sum = 0
for num in mylist:
sum += num
return sum
# Take the average of a list of numbers
# (what to return if the list is empty)
def avg_list(mylist, default):
if len(mylist) == 0:
return default
else:
return sum_list(mylist) / len(mylist)
grades1 = input("Enter first list of grades: ").split()
grades2 = input("Enter second list of grades: ").split()
avg1 = avg_list(float_list(grades1), 0)
avg2 = avg_list(float_list(grades2), 0)
print("Averages: first={:.2f}, second={:.2f}".format(avg1, avg2))
if avg1 > avg2:
print("First wins!")
elif avg2 > avg1:
print("Second wins!")
else:
print("Tie!")