Search code examples
pythonpython-3.xfractions

How to handle error during python object creation


I am attempting to write a class to help me output mixed fractions.

This code works fine for my purpose

class mixed_fraction():
    from fractions import Fraction
    '''Returns mixed fractions in tuple format
    (whole_part, numerator, denominator)
    '''
    def __init__(self, numerator = 0, denominator = 1):
        self.numerator = numerator
        self.denominator = denominator
    def mixed(self):
        if self.denominator == 0:
            return 'Denominator cannot be Zero'
        quotient = int(self.numerator/self.denominator)
        remain = self.numerator - (self.denominator * quotient)
        frac = Fraction(remain, self.denominator)
        return (quotient, frac.numerator, frac.denominator)

Sample inputs

print(mixed_fraction(3, 2).mixed())
print(mixed_fraction(1, 5).mixed())
print(mixed_fraction(1, 0).mixed())
print(mixed_fraction().mixed())
print(mixed_fraction(7, 2).mixed())
print(mixed_fraction(769, 17).mixed())
print(mixed_fraction(384, 256).mixed())

Outputs

(1, 1, 2)
(0, 1, 5)
Denominator cannot be Zero
(0, 0, 1)
(3, 1, 2)
(45, 4, 17)
(1, 1, 2)

But I want to be able to call out the zero error at the creation of the object because it makes no sense to create a fraction with a zero divisor in the first place. So I need help on catching that error on time.

class mixed_fraction():
    from fractions import Fraction
    '''Returns mixed fractions in tuple format
    (whole_part, numerator, denominator)
    '''
    def __init__(self, numerator = 0, denominator = 1):
        self.numerator = numerator
        self.denominator = denominator
        if denominator == 0:
            return 'Denominator cannot be Zero'
    def mixed(self):
        quotient = int(self.numerator/self.denominator)
        remain = self.numerator - (self.denominator * quotient)
        frac = Fraction(remain, self.denominator)
        return (quotient, frac.numerator, frac.denominator)

The problem is that __init__ must return None. So I am at a loss at how to go about fixing this


Solution

  • When you want to generate an error in Python, you don't return a value: you raise an error.

    In this case, the most appropriate would be a ZeroDivisionError:

    def __init__(self, numerator = 0, denominator = 1):
        self.numerator = numerator
        self.denominator = denominator
        if denominator == 0:
            raise ZeroDivisionError('Denominator cannot be Zero!')
    

    ZeroDivisionError is itself an specialization of "ArithmeticError" - but you could further custmize the error for your purposes by sub-classing it:

    class ZeroDenominatorError(ZeroDivisionError): 
        pass
    

    The main advantage of it is that the program flow can fall through layers of code that don't handle the error properly with a try...except block - up to the place where you catch it - otherwise you have to put an if statement in all places you instantiate your class to check for the type of return value.

    So - that is the correct way to handle it. Now, depending on design, it may be desirable to have the instantiation of a class to fail and return another value ("None" would be better than an error message - to have an error, just raise an exception). In that case, you should write the __new__ method instead of __init__. Unlike __init__, it has to return the newly created instance. (And it also has to create that new instance by properly calling the __new__ method of the superclass):

    from fractions import Fraction
    
    class MixedFraction(Fraction):
        def __new__(cls, numerator=0, denominator=1):
            if not denominator:
                return None
            instance = super().__new__(cls, numerator, denominator)
            return instance
        def mixed(self):
            quotient = self.numerator // self.denominator
            remain = self.numerator - (self.denominator * quotient)
            return (quotient, remain, self.denominator)
    

    As for Hilliad's suggestion in his answer, this inherits from "Fraction" directly - meaning it can be used wherever Fraction is used, but it has the extra "mixed" method. Notice that inheriting from Fraction would require you to write __new__ instead of __init__ if you were manipulating the numerator and denominator parameters anyway: they are set in the object at the time of instantiation, in native (C) code, and can't be further changed.