COMP1701-004

fall 2023

lec-12

Hump Day!

Here Comes November

RECALL

Tracing code involving decisions adds a new level of fun to the proceedings.

Let's do 2 examples.

RECALL

def bladder_discomfort(trip_len):
    level = ""
    if trip_len > 4:
        level = "mild"
    elif trip_len > 7:
        level = "painful"
    elif trip_len > 10:
        level = "critical"
    else:
        level = "normal"

    return level
What does bladder_discomfort(11) return?

trace #1

RECALL

snoo = "woo"
b = 10
zob = ""

if (len(snoo) > b or len(zob) > 0):
    zob = snoo
    snoo = snoo.upper()
else:
    b = snoo.upper()
    snoo = zob + str(b)

print(snoo)
print(b)
print(zob)

if ("o" in snoo):
  print(snoo.replace("o", "x"))
else:
  print(snoo.replace("o", "u"))
What is displayed when this code is run?

trace #2

let's talk about these  things today:

 boolean-returning functions

 branching boo-boo update

 debugging

 wishful coding

boolean-returning functions

boolean-returning functions

Task
Make a function northern(lat_deg) that returns True if lat_deg is a northern latitude and False otherwise.
Then use that function in another function symbol_for(lat_deg), that returns "N" if lat_deg is a northern latitude, and "S" otherwise.

Assumption!
lat_deg is an int guaranteed to be in -90 ... 90 - but no 0 allowed!

A northern latitude has a latitude > 0.

boolean-returning functions

def northern(lat_deg: int) -> bool:
  return lat_deg > 0

Task
Make a function northern(lat_deg) that returns True if lat_deg is a northern latitude (and False otherwise).
....

🙋🏻‍♂️❓🙋🏻‍♀️Create a northern(lat_deg) function. It's got a one-line body.

Task
Make a function northern(lat_deg) that returns True if lat_deg is a northern latitude and False otherwise.
Then use that function in another function symbol_for(lat_deg), that returns "N" if lat_deg is a northern latitude, and "S" otherwise.

Let's show some different ways of finishing the second part of our task.

First, though - you try!

boolean-returning functions

Task
Make a function northern(lat_deg) that returns True if lat_deg is a northern latitude (and False otherwise).
Then use that function in another function symbol_for(lat_deg), that returns "N" if lat_deg is a northern latitude, and "S" otherwise.

def northern(lat_deg: int) -> bool:
    return lat_deg > 0


def symbol_for(lat_deg: int) -> str:
    if northern(lat_deg):
        return "N"
    else:
        return "S"

Easy to follow.
Is the else necessary?

Variant 1: if/else with returns in each

boolean-returning functions

Task
Make a function northern(lat_deg) that returns True if lat_deg is a northern latitude (and False otherwise).
Then use that function in another function symbol_for(lat_deg), that returns "N" if lat_deg is a northern latitude, and "S" otherwise.

def northern(lat_deg: int) -> bool:
    return lat_deg > 0


def symbol_for(lat_deg: int) -> str:
    if northern(lat_deg):
        return "N"
    return "S"

when you return, you leave the function!

Variant 2: if with return, no else

boolean-returning functions

Task
Make a function northern(lat_deg) that returns True if lat_deg is a northern latitude (and False otherwise).
Then use that function in another function symbol_for(lat_deg), that returns "N" if lat_deg is a northern latitude, and "S" otherwise.

def northern(lat_deg: int) -> bool:
    return lat_deg > 0


def symbol_for(lat_deg: int) -> str:
    symbol = ""
    if northern(lat_deg):
        symbol = "N"
    else:
        symbol = "S"
    return symbol

Assign a variable a temporary dummy value, then re-assign in each branch.

Finally, return the variable.

Variant 3: if/else with standard single return

boolean-returning functions

Task
Make a function northern(lat_deg) that returns True if lat_deg is a northern latitude (and False otherwise).
Then use that function in another function symbol_for(lat_deg), that returns "N" if lat_deg is a northern latitude, and "S" otherwise.

def northern(lat_deg: int) -> bool:
    return lat_deg > 0


def symbol_for(lat_deg: int) -> str:
    if northern(lat_deg):
        symbol = "N"
    else:
        symbol = "S"
    return symbol

Variant 4: if/else with Python-only single return

Assign value to a variable each branch.

Finally, return the variable.

Won't work in C++ or Java!

JP! Run this in PythonTutor to illustrate!

boolean-returning functions

Task
Make a function northern(lat_deg) that returns True if lat_deg is a northern latitude (and False otherwise).
Then use that function in another function symbol_for(lat_deg), that returns "N" if lat_deg is a northern latitude, and "S" otherwise.

def northern(lat_deg: int) -> bool:
    return lat_deg > 0


def symbol_for(lat_deg: int) -> str:
    symbol = "S"
    if northern(lat_deg):
        symbol = "N"
    return symbol

Variant 5: if/else single return w/ default var value

Choose a default value for a shared variable. Re-assign to that variable if necessary.

Finally, return that variable.

boolean-returning functions

So which way is best

Follow whoever is paying you.

branching boo-boos

REVISITED

Need to add one - and clarify another.

if lives_left > 0:
  print("Continue?")
else lives_left <= 0:
  print("Game over!")

boo-boo #2

Review

Bad Syntax

🙋🏻‍♂️❓🙋🏻‍♀️What's the problem? How do we correct it?

Review

tank_empty = True

if tank_empty == True:
  print("The tank...she be empty!")
else:
  print("We got gas - let's roll!")

boo-boo #1

if 1 <= dial <= 10:
  print("The dial is pleased.")
  

boo-boo #3

if amount < 0:
  print("Invalid amount.")
elif amount >= 0 and amount < 40:
  print("Add some stuffs.")
else:
  print("Yay! Free stuffs.")

boo-boo #5

Bad Practices

if user_choice == "black" or "white":
  print("Your world is very...plain.")

boo-boo #4

we need to talk 'bout this

BTW - you don't have to memorize the numbers...they're just an arbitrary thing I made up!

if 1 <= dial <= 10:
  print("The dial is pleased.")
  

Mmmmm...humble pie.

This is called a chained comparison.

Most languages - particularly the ones you'll encounter for the rest of your degree - don't allow this sort of thing.

But Python does.

I did not know this until I started looking through your labs this weekend!

That being said, I'd be wary of getting too comfortable using it.

if a != b != c:
  # do something
  

Not only will you NOT be able to use it in your future classes, but it has a few teeth. For example:

Do you think this means "do something if a, b, and c are all different"?

What if a and c are 3, but b is 1?

A noo boo-boo.

def discount(num_items: int) -> float:
  '''Return discount depending on number of items purchased.'''
  if num_items <= 1:
    return 0
  elif num_items <= 3:
    return 0.1
  elif num_items >3:
    return 0.2
  

boo-boo #6

Why is our editor griping?

🙋🏻‍♂️❓🙋🏻‍♀️How do we correct this?

debugging

debugging

A (software) bug is an error in source code that causes that code to not do what it's supposed to do.

Debugging is the act of finding and removing bugs.

debugging - types of bugs

We can group bugs into 3 broad categories:

  1. syntax errors
  2. runtime errors
  3. logic errors

debugging - types of bugs

syntax errors

What's wrong?

The code violates one or more syntax rules and so the poor interpreter can't even run the code, because it can't interpret it.

How do I know I have one?

Oh, don't worry - your coding tools will let you know,

How should I feel when I see one?

Joyous, with perhaps a hint of embarrassment. 

debugging - types of bugs

syntax errors - examples

import sin from math

def skill_level(player: string) -> int
    result = -1
    very skilled = False or player == 'krom"
        if len(player) =< 3:
            result = 2
    return result / sin result

def main:
    skill_level("Mr. Tibbs")

main()

Taste the rainbow!

How many can you find?

pro tip
if you can't find the error on the line being reported, look UP one line!

debugging - types of bugs

runtime errors

What's wrong?

The code, while syntactically correct, attempts to do something that will go                 when the code is run.

How do I know I have one?

Your terminal will vomit forth a traceback and a pall will settle upon you.

How should I feel when I see one?

Slightly confused until you read the traceback...after which, irritated.

debugging - types of bugs

runtime errors - example 1

def ask_for_skill_level() -> int:
    return input("What's your skill level (1-10)? ")


def main():
    skill_level = ask_for_skill_level()
    if skill_level > 3:
        print("Wow, you have mad skillz!")
    else:
        print("You are not worthy of my time.")


main()

What will happen no matter what the user enters?

Do NOT neglect the clues!

debugging - types of bugs

runtime errors - example 2

def ask_for_num_guests() -> int:
    return int(input("How many guests? "))


def ask_for_num_spiders() -> int:
    return int(input("How many spiders? "))


def main():
    num_guests = ask_for_num_guests()
    num_spiders = ask_for_num_spiders()

    num_spiders_per_guest = num_guests // num_spiders

    print(f"Each guest should get {num_spiders_per_guest} spiders.")


main()

debugging - types of bugs

What will happen if the user enters 0 for the number of spiders?

What will happen if the user enters "none" for the number of guests OR spiders?

logic errors

What's wrong?

The code is syntactically correct and runs without blowing up...but that thing it's supposed to do? Nope - it don't do that. 

How do I know I have one?

When you run the code, something seems...off. Either a lot, or more terrifyingly, just a smidge.

How should I feel when I see one?

Dread, followed by an odd mix of despondency and grim determination.

debugging - types of bugs

logic errors - example 1

debugging - types of bugs

MM_PER_CM = 100

def get_side_len_cm():
  len_as_text = input("Length of side in cm? ")
  return float(len_as_text)
  
def squarea(side_len):
  return side_len ** 2

def display_result(side_len, area):
  print(f"A {side_len}cm x {side_len}cm square has area {area:.1f}mm^2")
  
def main():
  side_len_cm = get_side_len_cm()
  side_len_mm = side_len_cm * MM_PER_CM
  area = squarea(side_len_mm)
  display_result(side_len_cm, area)
  
main()

oops

logic errors - example 2

debugging - types of bugs

dd

def extra_cashiers_avail(num_cashiers):
    return num_cashiers > 1


def lines_are_too_long(len_longest_line):
    return len_longest_line > 5


def is_weekend(day_of_week):
    return day_of_week == "sa" or day_of_week == "su"


def should_open_checkout(dow, num_cashiers, len_longest_line):
    return is_weekend(dow) and \
        extra_cashiers_avail(num_cashiers) and \
        lines_are_too_long(len_longest_line)


def main():
    dow = "su"
    longest_line = 7
    num_cashiers = 4

    if should_open_checkout(dow, longest_line, num_cashiers):
        print("open another checkout")


main()

oops

logic errors - example 3

debugging - types of bugs

  • return "critical" if the length of the trip is 10 or more,
  • return "painful" if the length is more than 7 but less than 10,
  • return "mild" if the length is more than 4 but less than or equal to 7,
  • return "normal" otherwise
def bladder_discomfort(trip_len):
    level = ""
    if trip_len > 4:
        level = "mild"
    elif trip_len > 7:
        level = "painful"
    elif trip_len > 10:
        level = "critical"
    else:
        level = "normal"

    return level

Desired behaviour of function:

debugging -
strategies

debugging strategies

You're going to encounter a lot of bugs in this field.

Welcome to your life.

debugging - types of bugs

you're going to screw up.

A lot.

For as long as you live.

It's ok - 

Whatever.

  1. Listen to your development tools - but don't listen blindly
  2. Rubber-duck debug 
  3. Get into a debugger's mindset: your view of reality is wrong. Accept it.
  4. Start at the scene of the crime and work backwards
  5. Find - and record - inputs AND expected outputs for each function
  6. Find - and record - poisonous inputs that break your functions
  7. Avoid typing things in manually during debugging; call broken functions with poisonous inputs (see #6)
  8. see what your code is actually doing by tracing it with poisonous inputs
    1. manually
    2. with print statements (aka trace statements)
    3. with a debugger - but don't step over until you predict what should happen

Here is a laundry list of general strategies you can use to help you squash them bugs!

wishful coding

wishful coding & helper functions

Let's make a tiny app.

It's for a tiny plane with a forgetful pilot.

It asks how much fuel there is and whether the door is closed.

 

If there is 100L or more of fuel and the door is closed, it displays Ready for takeoff!; otherwise, it shows Don't take off yet, ya goof!

wishful coding & helper functions

We'll use something called wishful coding (among other things)

This technique is quite fun to use. It doesn't work for every problem - but it works for a lot of them.

wishful coding & helper functions

def main() -> None:
    pass


main()

Let's start building the main() habit.

Just having a bunch of code in your source file creates a whole bunch of global variables. This ain't good news. (Google "global variables are evil" if you're skeptical!)

pass is a useful bugger - it doesn't do anything...except not blow up

wishful coding & helper functions

def main() -> None:
    fuel_level = ask_for_fuel_level()
    door_closed = ask_if_door_closed()

    if plane_ready_for_takeoff(fuel_level, door_closed):
        print("Ready for takeoff!")
    else:
        print("Don't take off yet, ya goof!")


main()

Next, we'll write the code we wished was there

It should read like English...and solve the problem.

These are helper functions - functions we create to give a name to a part of our algorithm. We should use them - a LOT.

wishful coding & helper functions

Next, we'll shut up our editor's whining by making stubs - functions that don't actually do anything useful (yet)

def ask_for_fuel_level() -> int:
    return -1


def ask_if_door_closed() -> bool:
    return False


def plane_ready_for_takeoff(fuel_level, door_closed) -> bool:
    return False


def main() -> None:
    fuel_level = ask_for_fuel_level()
    door_closed = ask_if_door_closed()

    if plane_ready_for_takeoff(fuel_level, door_closed):
        print("Ready for takeoff!")
    else:
        print("Don't take off yet, ya goof!")


main()

stubs!

wishful coding & helper functions

Now, make those helper functions actually do what you want! (1/3)

def ask_for_fuel_level() -> int:
    return int(input("How much fuel do you have? "))


def ask_if_door_closed() -> bool:
    return False


def plane_ready_for_takeoff(fuel_level, door_closed) -> bool:
    return False


def main() -> None:
    fuel_level = ask_for_fuel_level()
    door_closed = ask_if_door_closed()

    if plane_ready_for_takeoff(fuel_level, door_closed):
        print("Ready for takeoff!")
    else:
        print("Don't take off yet, ya goof!")


fuel = ask_for_fuel_level()
print(fuel)

test that function - just a bit!

wishful coding & helper functions

Now, make those helper functions actually do what you want! (2/3)

def ask_for_fuel_level() -> int:
    return int(input("How much fuel do you have? "))


def ask_if_door_closed() -> bool:
    response = input("Is the door closed (yes/no)? ")
    return response == "yes"


def plane_ready_for_takeoff(fuel_level, door_closed) -> bool:
    return False


def main() -> None:
    fuel_level = ask_for_fuel_level()
    door_closed = ask_if_door_closed()

    if plane_ready_for_takeoff(fuel_level, door_closed):
        print("Ready for takeoff!")
    else:
        print("Don't take off yet, ya goof!")


door_closed = ask_if_door_closed()
print (door_closed)

test that function!

wishful coding & helper functions

Now, make those helper functions actually do what you want! (3/3)

def ask_for_fuel_level() -> int:
    return int(input("How much fuel do you have? "))


def ask_if_door_closed() -> bool:
    response = input("Is the door closed (yes/no)? ")
    return response == "yes"


def plane_ready_for_takeoff(fuel_level, door_closed)  -> bool:
    SAFE_THRESHOLD = 100
    return door_closed and fuel_level >= SAFE_THRESHOLD


def main() -> None:
    fuel_level = ask_for_fuel_level()
    door_closed = ask_if_door_closed()

    if plane_ready_for_takeoff(fuel_level, door_closed):
        print("Ready for takeoff!")
    else:
        print("Don't take off yet, ya goof!")


plane_ready_for_takeoff(99, True)
plane_ready_for_takeoff(100, False)

test that function!

door_closed: T/F
fuel level boundaries: 99, 100, 101

try:
- T/99, T/100, T/101

- F/99, F/100, F/101

wishful coding & helper functions

All done - now test the whole thing.

def ask_for_fuel_level() -> int:
    return int(input("How much fuel do you have? "))


def ask_if_door_closed() -> bool:
    response = input("Is the door closed (yes/no)? ")
    return response == "yes"


def plane_ready_for_takeoff(fuel_level, door_closed)  -> bool:
    SAFE_THRESHOLD = 100
    return door_closed and fuel_level >= SAFE_THRESHOLD


def main() -> None:
    fuel_level = ask_for_fuel_level()
    door_closed = ask_if_door_closed()

    if plane_ready_for_takeoff(fuel_level, door_closed):
        print("Ready for takeoff!")
    else:
        print("Don't take off yet, ya goof!")


main()

lec-12

By Jordan Pratt

lec-12

branching boo-boos update | boolean-returning functions | debugging | wishful coding

  • 222