# Python: Matrix Manipulation module

I made a module called "matrix-py" in python to add, subtract, multiply, transpose matrices

I want to know how to improve the code quality and if there's something wrong about my code

here's the code on github: https://github.com/FaresAhmedb/matrix-py

#!/usr/bin/python3
"""Matrix Manipulation module to add, substract, multiply matrices.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
"""

# pylint: disable=C0103 # Variable name "m" is "iNvAlId-nAmE"

import argparse
import json

name = 'matrixmanp'
__version__ = '0.1'
__all__     = ['Matrix']

class MatrixError(Exception):

"""Error for the Matrix Object invalid operations"""

class Matrix:

"""Matrix Object: add, sub, mul, and a lot more"""

# Object Creation: START
def __init__(self, matrix) -> None:
"""Initialize matrix object."""
self.matrix = matrix

def __str__(self, dims=True):
"""Return the Matrix, the size of it (Activated when using print)"""
for row in self.matrix:
print(' '.join(map(str,row)))

if dims:
return self.__repr__()

return ''

def __repr__(self):
"""Return the matrix size (string representation of the object)"""
return '({}x{})'.format(len(self.matrix), len(self.matrix[0]))
# Object Creation: END

# Object Expressions: START
def __pos__(self):
"""Positive operator: +A | Return the matrix * 1 (copy)"""
result = list()

for i in range(len(self.matrix)):
result.append([])
for m in range(len(self.matrix[0])):
result[i].append(+self.matrix[i][m])

return Matrix(result)

def __neg__(self):
"""Negative operator: -A. | Returns the matrix * -1"""
result = list()

for i in range(len(self.matrix)):
result.append([])
for m in range(len(self.matrix[0])):
result[i].append(-self.matrix[i][m])

return Matrix(result)
# Object Expressions: END

# Object Math operations: START
"""Matrix Addition: A + B or A + INT."""
if isinstance(other, Matrix):
# A + B
result = list()

if (len(self.matrix)    != len(other.matrix) or
len(self.matrix[0]) != len(other.matrix[0])):
raise MatrixError('To add matrices, the matrices must have'
' the same dimensions')

for m in range(len(self.matrix)):
result.append([])
for j in range(len(self.matrix[0])):
result[m].append(self.matrix[m][j] + other.matrix[m][j])

else:
# A + INT
result = list()

for m in range(len(self.matrix)):
result.append([])
for i in range(len(self.matrix[0])):
result[m].append(self.matrix[m][i] + other)

return Matrix(result)

def __sub__(self, other):
"""Matrix Subtraction: A - B or A - INT."""
if isinstance(other, Matrix):
# A + B
result = list()

if (len(self.matrix)    != len(other.matrix) or
len(self.matrix[0]) != len(other.matrix[0])):
raise MatrixError('To sub matrices, the matrices must have'
' the same dimensions')

for m in range(len(self.matrix)):
result.append([])
for j in range(len(self.matrix[0])):
result[m].append(self.matrix[m][j] - other.matrix[m][j])
else:
# A + INT
result = list()

for m in range(len(self.matrix)):
result.append([])
for i in range(len(self.matrix[0])):
result[m].append(self.matrix[m][i] - other)

return Matrix(result)

def __mul__(self, other):
"""Matrix Multiplication: A * B or A * INT."""
if isinstance(other, Matrix):
# A * B
if len(self.matrix[0]) != len(other.matrix):
raise MatrixError('The number of rows in matrix A must be'
' equal to the number of columns in B matrix')

# References:
# https://www.geeksforgeeks.org/python-program-multiply-two-matrices
result = [[sum(a * b for a, b in zip(A_row, B_col))
for B_col in zip(*other.matrix)]
for A_row in self.matrix]
else:
# A * INT
result = list()

for m in range(len(self.matrix)):
result.append([])
for i in range(len(self.matrix[0])):
result[m].append(self.matrix[m][i] * other)

return Matrix(result)
# Object Math opertaions: END

# Object Manpulation: START
def transpose(self: list):
"""Return a new matrix transposed"""
result = [list(i) for i in zip(*self.matrix)]
return Matrix(result)

def to_list(self):
"""Convert Matrix object to a list"""
return self.matrix
# Object Manpulation: END

def main():
"""The CLI for the module"""
parser=argparse.ArgumentParser(
description = 'Matrix Minuplation module to add, substract, multiply'
'matrices.',
epilog = 'Usage: .. -ma "[[1, 2, 3], [4, 5, 6]]" -op "+" -mb'
' "[[7, 8, 9], [10, 11, 12]]"')

action="version",
version=__version__,
)

metavar='',
help='Size of A Matrix'
)

metavar='',
help='Transpose of A Matrix (-t "[[1, 2, 3], [4, 5, 6]]")'
)

metavar='',
help='Matrix A (.. -ma "[[1, 2, 3], [4, 5, 6]]")'
)

type=str,
metavar='',
help='Operator (.. -op "+", "-", "*")'
)

metavar='',
help='Matrix B (.. -mb "[[1, 2, 3], [4, 5, 6]]")'
)

type=int,
metavar='',
help='Integer (.. -i 69)'
)

args = parser.parse_args()

if args.size:
return Matrix(args.size)

elif args.transpose:
return Matrix(args.transpose).transpose()

elif args.matrixa:
if args.operator == '+':
print(Matrix(args.matrixa) + Matrix(args.matrixb)) \
if args.matrixb else \
print(Matrix(args.matrixa) + args.int)
elif args.operator == '-':
print(Matrix(args.matrixa) - Matrix(args.matrixb)) \
if args.matrixb else \
print(Matrix(args.matrixa) - args.int)
elif args.operator == '*':
print(Matrix(args.matrixa) * Matrix(args.matrixb)) \
if args.matrixb else \
print(Matrix(args.matrixa) * args.int)
else:
raise SyntaxError('The avillable operations are +, -, *')
else:
return parser.print_help()

if __name__ == '__main__':
main()

• Why not numpy? – Reinderien Apr 7 at 23:18
• reinventing-the-wheel ! – Fares Ahmed Apr 8 at 0:01

Interesting project, writing it python does come with some limitations. Numpy is mostly written in C and there are good reasons for it. The memory management is much better and most of the array-manipulation routines can be done in-place instead of copying back and forth. AFAIK, there is no equivalency for pure c-arrays in Python. A tuple is probably the closest you can get.

The constructor is a void function and has one argument. You are specifying the return type but AFAIK the __init__ function must be a void, so there is really no point in specifying it. On the contrary, the argument has no type hinting. A matrix is a 2d array which needs to be a list or any other sequence type. You are building logic that assumes that self.matrix is a list type in other methods. So, it should be the other way around - specify the argument type but not the return type.

Using print inside the __str__ method looks a bit strange, because python will look for this method when calling print(self).

In __repr__ you could use f-strings to format the string. But this might be a matter of taste.

The __pos__ does not actually do anything what I can see. If you want to copy the list, then there are built-in methods for doing so.

__neg__ Is just doing the unary - operator on all elements in self.matrix. You could set result by doing:

result = [[-x for x in y] for y in self.matrix]


The binary operations are the most interesting. Generally, I try to avoid building any logic on static type inference, especially when writing programs on a high level. With that said, there are smart ways of doing it in python - Have a look at functools.singledispatch. But I often feel that static type checking just convolutes the call stack, thus making it harder to traceback for the purpose of debugging. I prefer to leverage polymorphism and move any kind of type inference to the internal dispatcher. The only thing that differs between adding together two lists (or matrices) and adding a list with an int is that the list needs to use indexing. You could make sure that the argument other to __add__ is a Matrix type and always use indexing like this:

self.matrix[m,j] + other.matrix[m,j]


This means that a matrix can also be an integer and the constructor will either take a list or int type as argument. To handle the cases for when you are indexing an integer (which doesn't really make sense), you need to overload the __getitem__ method for the Matrix base class. It might look something like this:

def __getitem__(self, idx):
i, j = idx
return isinstance(self.matrix, int) and self.matrix or self.matrix[i][j]


But there might be better ways of doing it. You could for example use a custom iterator.

As a final note, subtraction can be implemented as a composition of multiplication and addition, because a - b <=> a + (b * -1). It is slightly more inefficient, but you would have to look at the assembly to draw any conclusions.