Home > Back-end >  if statement throwing up errors for no obvious reason? (Python)
if statement throwing up errors for no obvious reason? (Python)

Time:08-17

I am trying to build a basic (beginner) project for a BMI calculator that can accept both metric and imperial measurements, I am trying to include raise statements to display an error if the entered unit of measurement isn't valid like if the user were to enter both imperial and metric or neither. I believe I have used the statements correctly but even if it the unit is entered correctly it still displays the error.

import sys

unit = input("To begin, please input your units of measurement (Imperial or Metric) ")

#error if user doesn't enter imperial or metric
if unit.lower().__contains__("metric" or "imperial") == False:
    sys.tracebacklimit = 0
    raise Exception("An error has occurred!\n\nPlease check if your unit of 
    measurement is valid/spelled correctly")
    exit(1)

#error if user enters both imperial and metric
if unit.lower().__contains__("metric" and "imperial"):
    sys.tracebacklimit = 0
    raise Exception("An error has occurred!\n\nPlease check if your unit of 
    measurement is valid/singular")
    exit(1)

I've tried removing them and it works but I want to keep them. I've tried changing the further if statements to elif, to no avail. I have also tried messing around with the statements like changing if unit.lower().__contains__("metric" or "imperial") == False: to if not unit.lower().__contains__("metric" or "imperial")etc. and again had no success with it. Is there anything that can be done?

Edit: This is only happening when "imperial" is entered

CodePudding user response:

"metric" or "imperial" evaluates to "metric", as you can see by pasting it into the REPL:

>>> "metric" or "imperial"
'metric'

so unit.lower().__contains__("metric" or "imperial") is the same as saying "metric" in unit.lower(), which will of course be false (and produce your error) if the unit is "imperial":

>>> unit = "imperial"
>>> "metric" in unit
False

Instead you could use an or in between the tests:

>>> "metric" in unit or "imperial" in unit
True

or put the units into an iterable and use the any function:

>>> any(u in unit for u in ("metric", "imperial"))
True

CodePudding user response:

You are testing for the existence of True and False in your current code. An easier way to do it is to just use the in keyword, and splitting up the expected terms.

It is much more readable this way as well.

import sys

unit = input("To begin, please input your units of measurement (Imperial or Metric) ")

#error if user doesn't enter imperial or metric
if unit.lower() not  in ["metric", "imperial"]:
    sys.tracebacklimit = 0
    raise Exception("An error has occurred!\n\nPlease check if your unit of 
    measurement is valid/spelled correctly")
    sys.exit(1)

#error if user enters both imperial and metric
if "metric" in unit.lower() and "imperial" in unit.lower():
    sys.tracebacklimit = 0
    raise Exception("An error has occurred!\n\nPlease check if your unit of 
    measurement is valid/singular")
    sys.exit(1)

CodePudding user response:

or does not do what you think it does here. or will return the first non None or False it finds. For example: False or 12 = None or 12 = 12 or None = 12. Also you should try to avoid directly calling dunder (double underscored) methods when you can. __contains__ is used for the in function. A better implementiation would therefore be:

import sys

unit = input("To begin, please input your units of measurement (Imperial or Metric) ").lower()

#error if user doesn't enter imperial or metric
if "metric" not in unit and "imperial" not in unit:
    sys.tracebacklimit = 0
    raise Exception("An error has occurred!\n\nPlease check if your unit of measurement is valid/spelled correctly")
    exit(1)

#error if user enters both imperial and metric
if "metric" in unit and "imperial" in unit:
    sys.tracebacklimit = 0
    raise Exception("An error has occurred!\n\nPlease check if your unit of measurement is valid/singular")
    exit(1)

Edit: the reason that your code only errors when "imperial" is entered is because of the or behaviour I described. "metric" or "imperial" is evaluated to just "metric" so unit.lower().__contains__("metric" or "imperial") == False is the same as unit.lower().__contains__("metric") == False which is obviously not the behaviour you want.

  • Related