Search code examples
pythonclassmethodsoperator-overloading

Assignment works with no __setitem__ defined


I'm writing my own matrix class that works with Python Fraction instead of numbers.

I now need to overload [], so I could read and assign values to specific cells in the matrix without directly addressing the base list from the outer scope.

So here's the code:

from fractions import Fraction


def is_finite_decimal_denominator(n):
    """
    :param n: denominator
    :return: True if the denominator's factors are only 2 and 5
    """
    number = n
    while number != 1:
        divisible_by_5 = (number % 5 == 0)
        divisible_by_2 = (number % 2 == 0)
        if not divisible_by_2 and not divisible_by_5:
            return False
        if divisible_by_2:
            number = number // 2
        else:
            number = number // 5
    return True


def fraction_to_string(fraction):
    """
    :param fraction: print the fraction as a finite decimal one if possible
    :return: void
    """
    if fraction.denominator == 1:
        return str(fraction.numerator)
    if is_finite_decimal_denominator(fraction.denominator):
        return str(fraction.numerator / fraction.denominator)
    else:
        return f"{fraction.numerator}/{fraction.denominator}"


class Matrix_LA:

    def __init__(self, *arg):
        self.M = []
        for line in arg:
            self.M.append(list(map(Fraction, line.split())))
        self.n_rows = len(arg)
        self.n_cols = len(self.M[0])
        self.space = 8

    def __str__(self):
        str_mtrx = '+' + '-' * (self.n_cols * self.space) + '+\n'
        for line in self.M:
            str_mtrx += "|"
            for num in line:
                str_mtrx += f"{fraction_to_string(num): ^{self.space}}"
            str_mtrx += "|\n"
        str_mtrx += '+' + '-' * (self.n_cols * self.space) + '+\n'
        return str_mtrx

    def __getitem__(self, n):
        return self.M[n]


x = Matrix_LA("1/7 2 ", "3 4")
print(x, x.M)


x[1][1] = Fraction(1, 7)
print(x, x.M)

What strikes me unusual is that the line x[1][1] = Fraction(1, 7) works fine even though there's no __setitem__ method in my class yet. How does Python know that it should address my self.M list to perform the assignment even though I haven't written the __setitem__ yet?


Solution

  • The __setitem__ method only applies to the first level get or set operation. That is, x[1][1] = Fraction(1, 7) is executed by x.__getitem__(1).__setitem__(1, Fraction(1, 7)). To break it down:

    1. x[1] invokes the __getitem__ method of your class (Matrix_LA) and passing in the parameter 1, which is the index of the indexing operation.
    2. Your __getitem__ then returns self.M[n], which is a collection object of type list.
    3. The list object returned by your __getitem__ method in turn have a __getitem__ method and a __setitem__ method. Thus, when doing the last phase of the operation, it works because the object you are now preforming an set item operation does implement a __setitem__ method. To test, print(hasattr(list, '__setitem__')) will return True.

    Summary: All multi-leveled indexing, either get item or set item, depends on the current object that the operation is preforming on. Also to note is the fact that the object changes every time an indexing operation is preformed. In this case, it changed from object x of type Matrix_LA to a list object.