Fraction Class in Python I decided to train myself in OOP with a simple perfect precision Fraction class.

from __future__ import division
import doctest


class Fraction:
    """
    Implements a Fraction Class with perfect precision
    operations and utility methods. The built-in operators
    are overwritten to provide a more natural interface.
    """
    def __init__(self,num,den):
        if den == 0:
            raise ValueError("Denominator must not be zero.")

        self.num = num
        self.den = den

    @staticmethod
    def greatest_common_divisor(a,b):
        """
        Returns the greatest number 'n' existing such
        that a % n == 0 and b % n == 0.
        This number may be one if 'a' and 'b' are coprimes.

        &gt;&gt;&gt; Fraction.greatest_common_divisor(20,15)
        5
        """
        def common(a,b):
            return [i for i in a if i in b]

        def div(n):
            return [i for i in range(1,n+1) if n % i == 0]

        return max(common(div(a),div(b)))

    @staticmethod
    def invert(fraction):
        """
        Returns a fraction where the numerator is the previous
        denominator and vice-versa.

        &gt;&gt;&gt; Fraction.invert(Fraction(3,5))
        5/3
        """
        return Fraction(fraction.den,fraction.num)

    def from_string(text):
        """
        Generates a Fraction object from a string rapresentation
        of two integers seperated by '/'.

        &gt;&gt;&gt; Fraction.from_string('4/9') + Fraction.from_string('2/18')
        5/9
        """
        return Fraction(*[int(i) for i in text.split('/')])

    def simplify(self):
        """
        Returns an eqivalent but simpler Fraction.

        &gt;&gt;&gt; Fraction.simplify(Fraction(210,20))
        21/2
        """
        fact = self.greatest_common_divisor(self.num,
                                       self.den)
        return Fraction(self.num // fact, self.den // fact)

    def __mul__(self,fraction):
        """
        Fraction multiplication.

        &gt;&gt;&gt; Fraction(4,3) * Fraction(1,20)
        1/15
        """
        return Fraction.simplify(
            Fraction(self.num*fraction.num,
                       self.den*fraction.den))

    def __add__(self,fraction):
        """
        Fraction addition.

        &gt;&gt;&gt; Fraction(4,9) + Fraction(11,7)
        127/63
        """
        common_den = self.greatest_common_divisor(
            self.den,fraction.den)
        num1 = self.num * fraction.den
        num2 = fraction.num * self.den
        return Fraction.simplify(
            Fraction(num1+num2, fraction.den*self.den))

    def __sub__(self,fraction):
        """
        Fraction subtraction.

        &gt;&gt;&gt; Fraction(1,2) - Fraction(1,3)
        1/6
        """
        return self + Fraction(-fraction.num,
                                fraction.den)

    def __truediv__(self,fraction):
        """
        Fraction division.

        &gt;&gt;&gt; Fraction(4,8) / Fraction(9,2)
        1/9
        """
        return self * self.invert(fraction)

    def __repr__(self):
        """
        Returns a printable representation of the fraction
        that can also be fed back into the class via
        'Fraction.from_string'.
        This method is called automatically on printing.

        &gt;&gt;&gt; Fraction(5,8)
        5/8
        &gt;&gt;&gt; Fraction(2,9)
        2/9
        """
        return '{}/{}'.format(self.num, self.den)

def main():
    doctest.testmod()

if __name__ == "__main__":
    main()
The from_string() function is missing a @staticmethod decorator, and thus does not work in Python 2.



Your greatest_common_divisor() function works using brute force: it literally tests every possible divisor and picks the greatest one that is common to both.  A much faster method is the Euclidean Algorithm.

@staticmethod
def gcd(a, b):
    while b:
        a, b = b, a % b
    return a




Validation and error handling could be tightened up a bit.  


The rejection of floating-point arguments is inconsistent:


&gt;&gt;&gt; Fraction(3.14, 1)
3.14/1
&gt;&gt;&gt; Fraction.from_string('3.14/1')
Traceback (most recent call last):
  […]
  File "fraction.py", line 55, in &lt;listcomp&gt;
    return Fraction(*[int(i) for i in text.split('/')])
ValueError: invalid literal for int() with base 10: '3.14'


Instantiating a Fraction with strings initially appears to work, but fails in an odd way later.  Either both of these statements should work, or both should fail.


&gt;&gt;&gt; Fraction('1', '3')
1/3
&gt;&gt;&gt; Fraction('1', '3') + Fraction('2', '3')
Traceback (most recent call last):
  […]
  File "fraction.py", line 32, in div
    return [i for i in range(1,n+1) if n % i == 0]
TypeError: Can't convert 'int' object to str implicitly


In Fraction.from_string(), the abstraction is slightly leaky.  I would prefer to see a ValueError instead of the following:


&gt;&gt;&gt; Fraction.from_string('1/3/5')
Traceback (most recent call last):
  File "&lt;stdin&gt;", line 1, in &lt;module&gt;
  File "fraction.py", line 55, in from_string
    return Fraction(*[int(i) for i in text.split('/')])
TypeError: __init__() takes 3 positional arguments but 4 were given


Personally, I would choose to raise a ZeroDivisionError if the denominator is 0.


&gt;&gt;&gt; Fraction(1, 0)
Traceback (most recent call last):
  File "&lt;stdin&gt;", line 1, in &lt;module&gt;
  File "fraction.py", line 13, in __init__
    raise ValueError("Denominator must not be zero.")
ValueError: Denominator must not be zero.