How is __setitem__() working with 4 arguments? - python

I'm writing a Matrix class with the [] operator overloaded.
Since my self.matrix which holds the matrix is a list of lists, the __getitem__() method as per convention takes 2 parameters self, index, returns a list(row) which can further be subscripted.
But what about __setitem__()? Isn't it supposed to take only 3 parameters by definition? I have tried it with 4 parameters and it is somehow working fine.
I know a tuple argument can be passed in to access the matrix elements but I would like to know why is this working with 4 parameters? Is this undefined behavior? If I write m_obj[rindex][cindex] = val, it works flawlessly!
Also if I just do m_obj[rindex] = val, I get the following error:
Traceback (most recent call last):
File "<pyshell#9>", line 1, in <module>
a[1] = 1
TypeError: __setitem__() missing 1 required positional argument: 'val'
But this indeed is what I want except the positional argument missing was cindex not val.
On the contrary, if I add an additional parameter to __getitem__(), the code doesn't work :
def __getitem__(self, rindex, cindex):
return self.matrix[rindex][cindex]
And I get this error both while getting and setting:
>>> a[1][1] = 1
Traceback (most recent call last):
File "<pyshell#21>", line 1, in <module>
a[1][1] = 1
TypeError: __getitem__() missing 1 required positional argument: 'cindex'
Here's the code:
class DimensionError(BaseException):
pass
class Matrix:
def __init__(self, rows, cols):
self.rows = rows
self.cols = cols
self.matrix = [[] for i in range(self.rows)]
for i in range(self.rows):
self.matrix.app
for j in range(self.cols):
self.matrix[i].append(0)
def __str__(self):
matrep = ''
for i in self.matrix:
matrep += str(i) + '\n'
return matrep
def __getitem__(self, index):
return self.matrix[index]
def __setitem__(self, rindex, cindex, val):
self.matrix[rindex][cindex] = val
def __add__(self, secmat):
if self.rows != secmat.rows or self.cols != secmat.cols:
raise DimensionError('Incompatible Matrices for Addition')
newmat = Matrix(self.rows, self.cols)
for i in range(self.rows):
for j in range(self.cols):
newmat[i][j] = self[i][j] + secmat[i][j]
return newmat
def __sub__(self, secmat):
if self.rows != secmat.rows or self.cols != secmat.cols:
raise DimensionError('Incompatible Matrices for Subtraction')
newmat = Matrix(self.rows, self.cols)
for i in range(self.rows):
for j in range(self.cols):
newmat[i][j] = self[i][j] - secmat[i][j]
return newmat
def __matmul__(self, secmat):
if self.cols != secmat.rows:
raise DimensionError('Incomatible Matrices for Multiplication. Product is undefined')
newmat = Matrix(self.rows, secmat.cols)
for i in range(self.rows):
for j in range(secmat.cols):
for k in range(secmat.rows):
newmat[i][j] += (self[i][k] * self[k][j])
return newmat
def __mul__(self, secmat):
return self.__matmul__(secmat)
#Driver
a = Matrix(2, 2)
b = Matrix(2, 2)
print('Enter elements of first matrix:')
for i in range(a.rows):
for j in range(a.cols):
a[i][j] = int(input(f'Enter element [{i}{j}] >>>'))
print('Enter elements of second matrix:')
for i in range(b.rows):
for j in range(b.cols):
b[i][j] = int(input(f'Enter element [{i}{j}] >>>'))
print('Matrix a: ')
print(a)
print('Matrix b: ')
print(b)
print('Multiplication is:')
print(a # b) # or a * b
I have tried it in Python 2.7 without f-strings and # operator and it works just the same .
Can someone explain what is going on under the hood?
Thanks in Advance!

It isn't working. It isn't being called at all.
When you do m_obj[rindex][cindex] = val, Python calls __getitem__ to get the value of m_obj[rindex]. Then it will call the __setitem__ method of whatever that value is. You can prove this to yourself by putting a print within your method.

Related

I want to print a Matrix in an specific format using the __str__ method [duplicate]

This question already has answers here:
How can I use `return` to get back multiple values from a loop? Can I put them in a list?
(2 answers)
Why does the print function return None?
(1 answer)
Can I return several lines using __str__?
(2 answers)
Closed 6 months ago.
I wanna print an all zeros matrix this way:
0 0 0
0 0 0
0 0 0
In order to do that i gotta use the __str__ method. This is what I've gotten so far:
class Matrix:
def __init__(self, m, n):
self.rows = m # rows
self.cols = n # columns
self.matrix = [] # creates an array
for i in range(self.rows):
self.matrix.append([0 for i in range(self.cols)])
def __str__(self):
# string = ""
for element in self.matrix:
return print(*element)
a_matrix = Matrix(3, 3)
print(a_matrix)
But when i run the code, there's an error:
Traceback (most recent call last):
File "C:\Users\DELL USER\Google Drive\Programacion\Negocios\main.py", line 72, in <module>
print(a_matrix)
TypeError: __str__ returned non-string (type NoneType)
0 0 0
Process finished with exit code 1
Note how i'm using return print(*element), usually, out of the str method it works just fine but when i use it that way, it stops working. Is there a way i can covert that print to a string so i can get rid of the error?
def __str__(self): needs to return a string. You can do this:
class Matrix:
def __init__(self, n, m):
self.matrix = []
for i in range(n):
self.matrix.append([0 for i in range(m)])
def __str__(self):
outStr = ""
for i in range(len(self.matrix)):
for c in self.matrix[i]:
outStr += str(c)
outStr += "\n"
return outStr
matrix = Matrix(3, 3)
print(matrix)
a_matrix has a property "matrix" print that?
class Matrix:
def __init__(self, m, n):
self.rows = m # rows
self.cols = n # columns
self.matrix = [] # creates an array
for i in range(self.rows):
self.matrix.append([0 for i in range(self.cols)])
def __str__(self):
# string = ""
for element in self.matrix:
return print(*element)
a_matrix = Matrix(3, 3)
for i in range(a_matrix.rows):
for j in range(a_matrix.cols):
print(a_matrix.matrix[i][j], end = " ")
print()

Error of argument in method of matrix class - Python

I create matrix class in python 3 (so far I have only create one method):
class Matrix() :
__rows = []
__columns = []
def SetRows(self,rows) :
self.__rows = rows
i = 0
while i < len(rows[0]) :
a = []
j = 0
while j < len(rows) :
a.append(rows[j][i])
j += 1
self.__columns.append(a)
i += 1
m = Matrix
m.SetRows([[0,8,56],[98,568,89]])
But it gives this error:
Traceback (most recent call last):
File "f:\PARSA\Programming\Python\2-11-2.py", line 14, in <module>
m.SetRows([[0,8,56],[98,568,89]])
TypeError: SetRows() missing 1 required positional argument: 'rows'
I've entered 'rows' argument. Obviously, I don't need to enter 'self'. I use VS Code for IDE. Thanks for your help.
Everything's fine with your function.
You just forgot the brackets when instantiating m=Matrix(). So the interpreter thinks you have to specify self, since it didn't recognize the class.
EDIT:
I've just recognized another problem. You actually created an infinite loop with those while loops. If you don't addition-assign i and j those will always stay below len(rows[0]) and len(rows) respectively.
So:
class Matrix() :
__rows = []
self.__columns = []
def SetRows(self,rows) :
self.__rows = rows
i = 0
while i < len(rows[0]) :
a = []
j = 0
while j < len(rows) :
a.append(rows[j][i])
j += 1
self.__columns.append(a)
i += 1
m = Matrix()
m.SetRows([[0,8,56],[98,568,89]])

Calling a method within other method definitions and 'object has no attribute'

So I've been solving problems utilizing classes for the sake of practicing, but I've been having troubles with calling a method defined in a class, within a method definition of the same class
class Hive:
def __init__(self, arr):
self._arr = arr
self._index = ['Re','Pt','Cc','Ea','Tb','Cm','Ex']
self._number = [0] * 7
self._ratio = []
self._len = len(self._arr)
def number(self):
for i in range(7):
for j in range(self._len):
if self._index[i] == self._arr[j]:
self._number[i] += 1
return self._number
def rate(self):
population = self._arr.number()
for x in range(7):
self._ratio.append(population[x] / self._len)
return self._ratio
def prnt(self):
population2= self._arr.number()
rate2 = self._arr.rate()
for k in range(7):
print("%s %d %.2f" % (self._index[k], population2[k], rate2[k]))
print("Total", str(self._len), "1.00")
arr = input().split()
Colony = Hive(arr)
Colony.prnt()
This ends up with an error that states the following:
Traceback (most recent call last):
File "inee.py", line 33, in <module>
Colony.prnt()
File "inee.py", line 25, in prnt
population2= self._arr.number()
AttributeError: 'list' object has no attribute 'number'
So the only way I could remedy this with my current knowledge was to take care of list assignments outside the method definitions:
class Hive:
def __init__(self, arr):
self._arr = arr
self._index = ['Re','Pt','Cc','Ea','Tb','Cm','Ex']
self._number = [0] * 7
self._ratio = []
self._len = len(self._arr)
def number(self):
for i in range(7):
for j in range(self._len):
if self._index[i] == self._arr[j]:
self._number[i] += 1
return self._number
def rate(self, array):
for x in range(7):
self._ratio.append(array[x] / self._len)
return self._ratio
def prnt(self, array2, array3):
for k in range(7):
print("%s %d %.2f" % (self._index[k], array2[k], array3[k]))
print("Total", str(self._len), "1.00")
arr = input().split()
initial = Hive(arr)
population = initial.number()
rateList = initial.rate(population)
initial.prnt(population, rateList)
This yielded results I wanted, but what's wrong with the first one? Is that not a correct way of calling methods within a method definition within a class?
By callint prnt()-method, you are referring to self._arr. This is not your Hive-object which has the number()-method. It's simply a list. A list does not have a number() attribute. The way you wrote your script you can only call number()-method on the Hive-object itself: self.number(), instead of self._arr.number().
This counts for rate()-method and prnt()-method:
def rate(self):
population = self.number() # <-- here
for x in range(7):
self._ratio.append(population[x] / self._len)
return self._ratio
def prnt(self):
population2 = self.number() # <-- here
rate2 = self.rate() # <-- and here
for k in range(7):
print("%s %d %.2f" % (self._index[k], population2[k], rate2[k]))
print("Total", str(self._len), "1.00")
The second variant you provided cannot work, because you're using number()-method on Hive-object which has no longer a defined attribute number(), because you outsourced it. But you can use it now as a function like this:
>>> arr = ["This", "is", "a", "test", "Re", "Cc", "Tb"]
>>> initial = Hive(arr)
>>> population = number(initial)
>>> print(population)
[1, 0, 1, 0, 1, 0, 0]

How to fix this OOP error?

I am trying to understand python oop. but it's not easy for me. thus i wrote
python OOP program (ex.2) for below procedural program (ex.1) but It's not working with below error.
ex.1
def factorial(n):
num = 1
while n >= 1:
num = num * n
n = n - 1
return num
f = factorial(3)
print f # 6
ex.2
class factorial:
def __init__(self):
self.num = 1
def fact(self,n):
while n>=1:
num = self.num * n
n = n-1
return num
f = factorial()
ne= factorial.fact(3)
print(ne)
error
Traceback (most recent call last):
File "F:/python test/oop test3.py", line 13, in ne= factorial.fact(3)
TypeError: fact() missing 1 required positional argument: 'n'
use the instance you created to call the method:
f = factorial() # creates instance of the factorial class
ne = f.fact(3)
Or to call using the class itself without assignment:
ne = factorial().fact(3) # < parens ()
print(ne)
You also have a mistake you should be using self.num or you will always get 1 as the answer, so:
class Factorial: # uppercase
def __init__(self):
self.num = 1
def fact(self, n):
while n >= 1:
self.num = self.num * n # change the attribute self.num
n -= 1 # same as n = n - 1
return self.num
If you don't return your method will return None but you will still increment self.num so if you don't want to return but want to see the value of self.num after you call the method you can access the attribute directly:
class Factorial:
def __init__(self):
self.num = 1
def fact(self, n):
while n >= 1:
self.num = self.num * n
n -= 1
ne = Factorial()
ne.fact(5) # will update self.num but won't return it this time
print(ne.num) # access the attribute to see it
There are three problems:
1) Logical error: num = self.num * n should be changed
to self.num = self.num * n, Here num is another variable which you are creating.
2) Logical error, But If 1st one is addressed it becomes Syntax error:
return num should be changed to return self.num
3) Syntax Error:
f = factorial()
ne= factorial.fact(3)
should be either changed to
ne = factorial().fact(3) or ne = f.fact(3)

Fixing array indices in Python

I'd like to have arrays that start from say an index of 4 and go to 9. I'm not interested in creating memory space for < 4, so how is best to proceed? My 2D code is as follows:
arr = [[ 0 for row in range(2)] for col in range(1, 129)]
>>> arr[0][0] = 1
>>> arr[128][0] = 1
Traceback (most recent call last):
File "<stdin>", line 1, in ?
IndexError: list index out of range
>>> arr[127][0] = 1
How can selectively just use the specific range i.e. where the last index runs from 1 to 128 inclusive not 0 to 127. This maybe obvious, but is there a way to do this?
Thanks for the suggestion for dicts, I have been avoiding these - I know - much of the code I'm converting is from C, but I think dictionaries might the saviour. Is there a way to do what I am asking with arrays?
For sparse arrays, use a dict:
sparseArray = {}
sparseArray[(0,0)] = 1
sparseArray[(128,128)] = 1
print sparseArray # Show the content of the sparse array
print sparseArray.keys() # Get all used indices.
You can simply emulate a list:
class OffsetList(object):
def __init__(self, offset=4):
self._offset = offset
self._lst = []
def __len__(self):
return len(self._lst)
def __getitem__(self, key):
return self._lst[key - self._offset]
def __setitem__(self, key, val):
self._lst[key - self._offset] = val
def __delitem__(self, key):
del self._lst[key - self._offset]
def __iter__(self):
return iter(self._lst)
def __contains__(self, item):
return item in self._lst
# All other methods go to the backing list.
def __getattr__(self, a):
return getattr(self._lst, a)
# Test it like this:
ol = OffsetList(4)
ol.append(2)
assert ol[4] == 2
assert len(ol) == 1
You have two options here. You can use sparse lists, or you can create a container type that basically has a normal list and a start index, such that when you request
specialist.get(4)
you actually get
specialist.innerlist[4 - startidx]
If you really wanted list semantics and all, I suppose you could do
class OffsetyList(list):
def __init__(self, *args, **kwargs):
list.__init__(self, *args)
self._offset = int(kwargs.get("offset", 0))
def __getitem__(self, idx):
return list.__getitem__(self, idx + self._offset)
def __setitem__(self, idx, value):
list.__setitem__(self, idx + self._offset, value)
# Implementing the rest of the class
# is left as an exercise for the reader.
ol = OffsetyList(offset = -5)
ol.extend(("foo", "bar", "baz"))
print ol[5], ol[7], ol[6]
but this seems very fragile to say the least.

Categories

Resources