Python – possible problem returning a value in a function which can rerun itself

Question:

# Python code to check input for "" and diving by zero error


def getNumber():
    x = input("Enter a number to divide 100 by: ") # string!!
                
    if x == "": # must check for this first as cant convert "" into int or float!
                # and eventually want to divide 100 by the user inputed number
        print("You entered 'no data', try again")
        del x # may as well kill x, as it's useless
        getNumber()

    # Code runs if input was not ""
    x=float(x)
    x=DivByZeroChecker(x)   # pass newly made float into into /zero func. and store
                            # and store returned value in x 
    return x


def DivByZeroChecker(x):
    try:
        test = 100 / x
        print(f"Passed the zero number checker test.    x = {x}")        
        return x # << do this if division is valid
    except ZeroDivisionError:
        print("You're trying to dividing by zero! Re-enter number...")
        del x # may as well kill this variable, as it's useless
        getNumber() # re-run the whole procedure
        
###################
# MAIN program code:
# ~~~~~~~~~~~~~~~~~~

num = getNumber()
print(f"FINAL SECTION: type(num)= {type(num)}")
print(f"100 / (your number), {num} =", (100.0/num))

Any user input (int or float) sees program behaving as I want it to, which is to do the final 100/num calculation. But enter 0 then 1 OR entering ""(pressing enter) followed by 1 gives this

CONSOLE OUTPUT:

Enter a number to divide 100 by: 0
You're trying to dividing by zero! Re-enter number...
Enter a number to divide 100 by: 1
Passed the zero number checker test.    x = 1.0
FINAL SECTION: type(num)= <class 'NoneType'>

ERROR:

"    print(f"100 / (your number), {num} =", (100.0/num))
TypeError: unsupported operand type(s) for /: 'float' and 'NoneType'

From which I presume there are two x objects. One the float which I’m happy with and want to use, but a NoneType, (="" ???, empty not even a string type)

And when I enter "" followed by a number, I also get an error.

CONSOLE OUTPUT:

Enter a number to divide 100 by: 
You entered 'no data', try again
Enter a number to divide 100 by: 1
Passed the zero number checker test.    x = 1.0

line 34, in <module>
    num = getNumber()

line 14, in getNumber
    x=float(x)
UnboundLocalError: local variable 'x' referenced before assignment

I really can’t see how these errors are coming up. Can anyone point out WHY/HOW
those errors are occuring and how to solve it? Thanks.

Asked By: Micklos

||

Answers:

The best way to validate numeric input is with try/except. In this case you also need to check for zero. There’s no need to execute division by zero because you can check that at the same time you validate the input.

Something like this:

def get_number():
    while True:
        value = input('Enter a number to divide 100 by: ')
        try:
            if value := float(value):
                return value
            raise ValueError('Zero not allowed')
        except ValueError as e:
            print(e)

num = get_number()
print(f'100 / {num} = {100/num}')

Note:

Python 3.8+ required for this

Answered By: DarkKnight

getNumber should use a simple while loop to ensure x is a valid float, not recursion*. It should also be responsible for deciding to get a new value if ZeroByDivChecker fails.

def getNumber():
    while True:
        x = input("Enter a number to divide 100 by: ")
        try:
            # There are lots of reasons why `float(x)` might fail;
            # let float itself check for them.
            x = float(x)
        except ValueError:
            print("Invalid float input {x!r}, try again")
            continue

        try:
            return DivByZeroChecker(x)
        except ZeroDivisionError:
            print("Cannot divide by zero", try again")
     
       

As you might guess, there’s little left for DivByZeroChecker to do.

def DivByZeroChecker(x):
    return 100/x
    

It’s not so much checking for zero division as it is simply wrapping the specific division operation. The final try statement in getNumber could simply inline the function. Whether you try the division and catch the exception or simply check if x == 0 first is up to you.

if x == 0:
    print("Cannot divide by zero, try again")
    continue

return 100/x

* Python cannot handle unbounded recursion, as it lacks tail-call optimization. Save recursion for operating on recursive data structures where the depth of the recursion tree will be relatively small.

Answered By: chepner