Python Python Basics All Together Now Handle Exceptions

Code and Theory Dev Team
PRO
Code and Theory Dev Team
Pro Student 31,013 Points

Raise ValueError makes another error message fail

Please see my code below. The error occurs when it asks how many tickets you'd like, you input a string, and it fails to coerce it to an int.

This is occurring because I passed the ValueError as err, but then it also passes an ugly error when you input a string and the program tries to coerce it into an int. Craig does not address this in the video. I originally used an if statement within the "else" of the "try except else" but wanted to try to imitate Craig's code. What would be a practical solution to fix Craig's code?

Thanks.

TICKET_PRICE = 10

tickets_remaining = 100

while tickets_remaining > 0:

    print("{} tickets remain!".format(tickets_remaining))
    name = input("Hello, what is your name?  ")
    print("Hi {}!  There are {} tickets remaining!".format(name,tickets_remaining))
    tickets_wanted = input("How many tickets would you like, {}?  ".format(name))
    try:
        tickets_wanted = int(tickets_wanted)
        #Raise a value error if request exceeds ticket count
        if tickets_wanted > tickets_remaining:
            raise ValueError("There are only {} tickets remaining!".format(tickets_remaining))
    except ValueError as err:
        print("Oh no something went wrong pls halp. {}  Please try again.".format(err))
    else:
        total_cost = tickets_wanted * TICKET_PRICE

        print("That will be ${}, {}.".format(total_cost,name))
        confirm_yes = input("Are you sure you want to proceed? (yes/no)  ") 
        confirm_yes = confirm_yes.lower()

        if confirm_yes == "yes":
            tickets_remaining -= tickets_wanted #what if tickets_wanted > tickets_remaining?
            print("SOLD!!!")
        else:
            print("WOOPS!!!")
print("Tickets are now sold out!".format(name))
Renato Guzman
Renato Guzman
40,899 Points

If you put a string. You get an error similar to: Oh no something went wrong pls halp. invalid literal for int() with base 10: 'Seven' Please try again. . Is the invalid literal for int() with base 10: 'Seven' the ugly error?

Code and Theory Dev Team
Code and Theory Dev Team
Pro Student 31,013 Points

Hi Renato, yes that is what I meant. Craig called it that in the video. Sorry for the confusion.

So it is passing the ACTUAL error message into err and displaying that, too. It works as intended when you ask for more tickets than are remaining.

Austin Loos
Austin Loos
3,809 Points

Ran into this same problem as well, but I found a solution that works, although it might not be the cleanest. So all we're doing here is essentially bringing the error to the users attention by calling a ValueError, but the thing is, we can really raise any error we want, like say a NameError, it doesn't always have to be a ValueError. We get that ugly error message because we're formatting err and passing whatever it stores onto the user, which works great for stopping overticketing errors, but its kinda lousy for the original value error we encountered because of what it passes back to the user. But if you create another exception, like the one below, you can get both messages to show cleanly.

 try:
            num_tickets = input("How many tickets would you like {}?    ".format(name))
            num_tickets = int(num_tickets)
            if num_tickets > tickets_remaining:
                raise NameError("There are only {} tickets remaining.".format(tickets_remaining))
except NameError as err:
            print("Oh no, we ran into an issue! {} Please try again.".format(err))
except ValueError:
            print("Oh no, we ran into an issue! Please try again.")
else:
            print("Your total is ${}, {}!".format(total_cost(num_tickets, TICKET_PRICE), name))
            confirm_purchase()
            tickets_remaining -= num_tickets

2 Answers

Eric McKibbin
MOD
Eric McKibbin
Treehouse Moderator 11,445 Points

Hi Code & Theory Team, Hi Austin,

It's not a great idea to use NameError for this as NameError is for when a program calls to a reference (or handle, or name) that is not defined.

If you need to pass a different message just include it in the error, if you don't want another message from the same error type to display you have two options.

The simple and not very good option is to add some conditional logic:

ERROR_MSG_START = "Oh no, we ran into an issue!"
ERROR_MSG_END = "Please try again."

try:
    num_tickets = input("How many tickets would you like {}?    ".format(name))
    num_tickets = int(num_tickets)
    if num_tickets > tickets_remaining:
        raise ValueError(f"There are only {tickets_remaining} tickets remaining.")
except ValueError as err:
    if "tickets remaining" in err:
      print(f"{ERROR_MSG_START} {err} {ERROR_MSG_END}")
    elif "invalid literal" in err:
      print(f"{ERROR_MSG_START} You must enter an integer {ERROR_MSG_END}")
    else:
      print(f"{ERROR_MSG_START} {ERROR_MSG_END}")
else:
    print(f"Your total is ${total_cost(num_tickets, TICKET_PRICE)}, {name}!")
    confirm_purchase()
    tickets_remaining -= num_tickets

The best way is to create a custom exception type:

class OutOfTicketsError(Exception):
  pass

try:
    num_tickets = input("How many tickets would you like {}?    ".format(name))
    num_tickets = int(num_tickets)
    if num_tickets > tickets_remaining:
        raise OutOfTicketsError(f"There are only {tickets_remaining} tickets remaining.")
except ValueError:
    print(f"{ERROR_MSG_START} {ERROR_MSG_END}")
except OutOfTicketsError as err:
    print(f"{ERROR_MSG_START} {err} {ERROR_MSG_END}")  
else:
    print(f"Your total is ${total_cost(num_tickets, TICKET_PRICE)}, {name}!")
    confirm_purchase()
    tickets_remaining -= num_tickets

Though I would still want to give the user more information on requiring an integer for the value error in the second example.

Cheers,

Eric

Austin Loos
Austin Loos
3,809 Points

Thanks Eric, that cleared things up. The first example may not be as clean as the second, but it's more in line with what we've learned so far, so it may be easier for other students to pick up on. Just a heads up though, you're going to get a TypeError if you try to iterate through err without coercing it to a string first. This works though.

if "tickets remaining" in str(err):
      print(f"{ERROR_MSG_START} {err} {ERROR_MSG_END}")

I like your custom exception solution much better, but we haven't covered classes yet, so it may be confusing to some who are learning python as their first language. Either way, thanks for the input; much better than my NameError bandaid.

Eric McKibbin
Eric McKibbin
Treehouse Moderator 11,445 Points

Cheers Austin,

I agree about the Custom Exception maybe being too much for an absolute beginner, the branching is absolutely okay for getting a programming running while learning, it's still good to know that there are better solutions available when you're ready for them.

Thanks for catching my TypeError :)

Let's keep it DRY and use one type cast instead of three (two of ours and the one print() does for us).

except ValueError as err:
    error_string = str(err)
    if "tickets remaining" in error_string:
        print(f"{ERROR_MSG_START} {error_string} {ERROR_MSG_END}")
    elif "invalid literal" in error_string:
        print(f"{ERROR_MSG_START} You must enter an integer {ERROR_MSG_END}")
    else:
        print(f"{ERROR_MSG_START} {ERROR_MSG_END}")

Happy Coding!

Brendan Lindsay
Brendan Lindsay
3,408 Points

In case this helps, I was able to handle this in a different way. I kept the ValueError except, but added an "if/else statement" with "continues" to handle the other two input issues.

TICKET_PRICE = 10
tickets_remaining = 100  

def calculate_price(num_of_tickets):
    return num_of_tickets * TICKET_PRICE

while tickets_remaining > 0:
    print("There are {} tickets remaining.".format(tickets_remaining))

    name = input("What is your name? ")
    requested_tickets = input("How many tickets would you like, {}? ".format(name))
    try:
        requested_tickets = int(requested_tickets)
    except ValueError as err:
          print("Please enter a whole number (Ex: 4)")
    else:
        if requested_tickets > tickets_remaining:
            print("Sorry, we do not that many tickets left.")
            continue
        elif requested_tickets < 0:
            print("Sorry, please enter a positive value.")
            continue
        else:
            order_price = calculate_price(requested_tickets)
            order_status = input("Alright {}, that will be ${}. Do you want to continue?  Y/N ".format(name,order_price))
        if order_status.upper() == 'Y':
            print("Great! Order confirmed.")
            tickets_remaining -= requested_tickets
        else:
            print("Your order has been cancelled.")
print("There are no more tickets remaining")