Search code examples
pythonerror-handlingtry-except

Try-except with NameError and TypeError


Can you please help me with the following. I am trying to catch two exceptions: 1) TypeError and 2)NameError. I use the following code below that estimates the average:

def calculate_average(number_list):
    try:
        if type(number_list) is not list:
            raise ValueError("You should pass list to this function")
    except ValueError as err:
        print(err)
        return
    try:
        average = sum(number_list)/len(number_list)
    
    except TypeError:
        print('List should contain numbers')
        return
    except NameError:
        print('List should contain numbers')
        return
 
    return average

The code works fine for:

print(calculate_average([1, 2, 3]))
print(calculate_average([1, 2, 'a']))

But when I use:

print(calculate_average([1, 2, a]))

I have the following error that was supposed to be captured by except:

NameError: name 'a' is not defined

Can you please help me with understanding the issue? (I use Spyder)


Solution

  • The NameError on a is raised in the calling scope, not when you attempt to use number_list. You would need to catch it there:

    try:
        print(calculate_average([1, 2, a]))
    except NameError:
        print("Variable not defined")
    

    However, you shouldn't be catching NameErrors at all. When they arise in testing, you should figure out what undefined name you are trying to use, and make sure it is defined. Like most exceptions, this isn't intended for flow control or dealing with easily fixed problems at runtime.


    Rather than littering your code with run-time type check, consider using type hints and static typecheckers like mypy to catch code that would produce a TypeError at runtime.

    # list[int] might be too restrictive, but this is a simplified
    # example
    def calculate_average(number_list: list[int]):
        
        average = sum(number_list)/len(number_list)
        return average
    

    They only error left here that mypy wouldn't catch is the attempt to divide by zero if you pass an empty list. That you can check for and handle. You can raise a ValueError, or just decide that the average of an empty list is 0 by definition.

    def calculate_average(number_list: list[int]):
        if not number_list:
            # raise ValueError("Cannot average an empty list")
            return 0
    
        return sum(number_list)/len(number_list)
    

    This is preferable to

    try:
        return sum(number_list)/len(number_list)
    except ZeroDivisionError:
        ...
    

    because it anticipates the problem before you go to the trouble of calling sum and len.