How to make "class1 * class2" behave like "class2 * class1"? - python

I've been watching Linear Algebra series made by 3Blue1Brown and I came up with this idea that I should write programs to compute the math, so I started to do it.
I wrote some methods. I wrote mul method for it. This method stretch out the vector by a given factor.
class Vector:
def __init__(self, data):
# [1]
# [1, 2, 3] --> [2]
# [3]
self.data = data
def __mul__(self, other):
if not isinstance(other, (int, float)):
raise TypeError("The second object(item) is not a number(integer, float)")
return Vector(list(map(lambda x: x * other, self.data)))
For example:
sample = Vector([1, 2])
When I execute this, it executes without errors:
print(sample * 10)
# it returns Vector([10, 20])
But when I execute this:
print(10 * sample)
It throws an error:
Traceback (most recent call last):
File "/home/jackson/Desktop/Matrices/MM.py", line 139, in <module>
print(10 * a)
TypeError: unsupported operand type(s) for *: 'int' and 'Vector'
I know the second one runs int.mul . So is there any way for the second one to behave like the first one?? Because technically there shouldn't be any difference between "Vector * int" and "int * Vector".
Here is the full code if you need --> link

Yes, you need to implement __rmul__ etc. See https://docs.python.org/3.7/reference/datamodel.html#emulating-numeric-types
Also, a really good linear algebra library already exists for python called numpy (but if you are implementing it yourself for learning purposes, just ignore that and have fun instead).

Related

trying to do math, int object is not callable

def square(x):
int(x)
return 2(x * x)
def my_map(func, arg_list):
result = []
for i in arg_list:
result.append(func(i))
return result
squares = my_map(square, [1,2,3,4,5])
print(squares)
i'm trying to pass a number to a function and have it print the result, i have a function for the equation def square(), and a function that receives the numbers and the function
I keep on getting this error:
"TypeError: 'int' object is not callable"
I'm new to programming and was watching corey shaffer in YouTube and he wrote a program similar to this one. I started to play around with it, and now I'm stuck.
I would like the print statement to print out arg_list(i) and have I go through def square(x) and have that answer stored in result
here's a fix to your square function (you were missing a * operator)
def square(x):
int(x)
return 2*(x * x)
however, according to your function name I'm guessing you wanted the function to return the square of x:
2 --> 4
3 --> 9
4 --> 16
in that case, here's also a bug fix:
def square(x):
return x**2
The line 2(x * x) is causing python to treat the integer 2 as a function with arguments x*x, and hence the error "TypeError: 'int' object is not callable"
In [16]: x = 1
In [17]: 2(x*x)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-17-14e610e542ba> in <module>
----> 1 2(x*x)
TypeError: 'int' object is not callable
To square a number, you need x**2 instead, or perhaps pow(x, 2)using the pow builtin
def square(x):
int(x)
return x**2
The problem is with 2(x * x) statement. In the Python programming language, we need to explicitly mention all the operators in expressions. Like, the statement should be 2*(x*x) and when you use rounded brackets, you are actually calling the function.
Also, the above program can be reduced to,
In [54]: list(map(lambda no: int(no)**2, [1, 2, 3, 4, 5]))
Out[54]: [1, 4, 9, 16, 25]

Magic methods with > 1 operands between MyClass and standard Class

Imagine I want to define a new number class. Say, RationalFractions or GaussIntegers, whatever. Of course, I can easily define a + b for two objects of MyClass. But i would like to be able to add an object of MyClass with some existing, like "integer" or "float". With the result having a relevant type (from my point of view). E.g. the result of GaussInteger + float = GaussInteger, RationalFraction + integer = RationalFraction, etc.
I guess I should somehow alter add for Object class, or "integer", "float"? Or there is a way to do it without meddling with the existing classes?
Edit. So, an example:
class RatFr:
def __init__(self, m, n=1):
self.m = m
self.n = n
def __add__(self, other):
temp = (RatFr(other) if type(other) == int else other)
return RatFr(self.m * temp.n + self.n * temp.m, self.n * temp.n)
def __str__(self):
return f'{self.m}/{self.n}'
a = RatFr(5,3)
b = 1
print(a)
print(a + b)
print(b + a)
I get as a result:
5/3
8/3
Traceback (most recent call last):
File "/Users/aleksej/PycharmProjects/Alex2/playaround.py", line 19, in <module>
print(b + a)
TypeError: unsupported operand type(s) for +: 'int' and 'RatFr'
Trying to convert self does nothing good. As soon as the first operand is int, python obviously looks for integer add method.
Yes. You will want to override __add__ from object, taking self and say x as parameters. You can then deal with x according to its type. Here you have a few options. You can do explicit type checking, but this is not very Pythonic. I would probably make a conversion function to convert from int, float, etc to your type and call it on x. Then you can do whatever addition would do between two objects of your type. This sort of call to a conversion function before doing an operation is done in the mpmath library, in the backend. Remember that you will need to check if the thing you are converting is already the right type.

Python: What does it mean when two objects are "multiplied" together using an asterisk (*)?

I am a bit of a Python newbie regarding classes. I have seen that some scripts include lines which appear to "multiply" instances of classes together. For example:
Z = X * Y
where X and Y are instances of two different classes.
What does the * symbol mean and how is it applied to classes (or instances of classes) in Python?
In most cases * pertains to multiplication, but I don't understand what it means to "multiply" classes.
Any info is appreciated.
The * operator just means "multiply".
But what it means for two objects to be multiplied is up to those objects' types to decide.
For all of the builtin and stdlib types, it means multiplication if that makes sense, or it's a TypeError if that doesn't make sense. For example:
>>> 2 * 3
6
>>> (1, 2, 3) * 3
(1, 2, 3, 1, 2, 3, 1, 2, 3)
>>> "abc" * "def"
TypeError: can't multiply sequence by non-int of type 'str'
Since multiplication doesn't make sense for class objects, they're one of the kinds of things where it's a TypeError:
>>> class C: pass
>>> C * 2
TypeError: unsupported operand type(s) for *: 'type' and 'int'
If you (or a third-party library author) create a new type, you can decide what multiplication means. The way you do this is covered under Emulating numeric types in the documentation, but the short version is that you define a __mul__ method:
class MyNumberyThing:
def __init__(self, n):
self.n = n
def __repr__(self):
return f'MyNumberyType({self.n})'
def __mul__(self, other):
if isinstance(other, MyNumberyThing):
return MyNumberyThing(self.n * other.n)
elif isinstance(other, int):
return MyNumberyThing(self.n * other)
elif isinstance(other, float):
raise TypeError("sorry, can't multiply by float because of precision issues")
else:
raise TypeError(f"sorry, don't know how to multiply by {type(other).__name__}")
Notice that this makes instances of MyNumberyThing multiplicable. It doesn't make MyNumberyThing itself multiplicable (MyNumberyThing isn't a MyNumberyThing, it's a type):
>>> n = MyNumberyThing(2)
>>> n * 3
MyNumberyType(6)
>>> MyNumberyThing * 3
TypeError: unsupported operand type(s) for *: 'type' and 'int'
Of course nothing's stopping you from defining something ridiculous:
class MySillyThing:
def __mul__(self, other):
self.storage = -other
print(f'I took your {other}')
>>> silly = MySillyThing()
>>> silly * 3
I took your 3
>>> silly.storage
-3
… except, of course, for the fact that nobody would understand your code. (And that "nobody" includes you, 6 months later trying to debug something you thought you were done with…)
As a side note, Python actually has two ways to spell "multiply": * and #.
The builtin types all ignore #, so you'll just get a TypeError if you try to use it. What it's there for is, basically, so NumPy can use * for elementwise multiplication, and # for matrix multiplication. (The # operator's magic method is even called __matmul__.) But of course other third-party libraries that similarly needed to do two different kinds of multiplication could use # for the second kind.

Adding zero to my complex number class

I'm creating my own complex number class (not using Python's built in one) and I'm running into a problem when I try to add zero to my complex number. For reference this is the error:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "display.py", line 25, in __init__
t.color(mandelbrot(c).getColor())
File "C:\Users\Joe\Desktop\Python\mandelbrot.py", line 10, in __init__
z = z*z + self.__starting_value
TypeError: unsupported operand type(s) for +: 'int' and 'complex'
Where self.__starting_value is the complex number.
The addition definition goes as follows:
class complex:
def __init__(self, a = 0, b = 0):
self.__real = float(a)
self.__imag = float(b)
def __add__(self, other):
return complex(self.__real + other.__real, self.__imag + other.__imag)
The solution should be simple enough but I am still learning Python and could use the help.
Two problems:
You are only handling addition of a complex object to a complex object, when you want to also be able to handle complex object + int.
When adding in this order (int + custom type), you need to add a reflected addition method (__radd__)
class complex:
def __init__(self, a = 0, b = 0):
self.__real = float(a)
self.__imag = float(b)
def __add__(self, other):
if isinstance(other, complex):
return complex(self.__real + other.__real, self.__imag + other.__imag)
else:
return complex(self.__real + other, self.__imag)
def __radd__(self, other):
return self + other
N.B.: It is considered bad style to shadow built-in names (like complex).
int + complex will first try to useint.__add__. complex + int will first try to use complex.__add__.
You need to implement complex.__radd__. See the related note in Emulating numeric types:
These functions are only called if the left operand does not support the corresponding operation and the operands are of different types.
You will need to handle the case where other is int in both complex.__add__ and complex.__radd__.
__add__ is for addition when the left-hand-side operand is of the type you're writing. When Python fails to call the int __add__ method, it tries __radd__. If you define __radd__ (with the same behavior), you'll get the result you want.

Duplicating Python int * numpy.array behavior

I'm attempting to build a class representation of a matrix complete with most of the normal mathematical operations. I've hit a snag with the scalar multiplication operation.
The relevant part of the code is as follows:
import numpy
class Matrix(object):
def __init__(self, array):
self.array = numpy.array(array, dtype=int)
def __mul__(self, other):
if type(other) == int:
return Matrix(other*self.array)
else:
raise ValueError("Can not multiply a matrix with {0}".format(type(other)))
The standard way the scalar multiplication is expressed is cA where c is a scalar and A is a matrix, so c*A in Python. However, this fails with a TypeError: unsupported operand type(s) for *: 'int' and 'Matrix' while A*c runs as expected (note the other*self.array). Thus I conclude that the * operand is defined for int and numpy.array.
What is this magic and how can I replicate the behavior?
You need a __rmul__ in your calss. For example if you add
def __rmul__(self, other):
return self.__mul__(other)
then:
>>> A = Matrix(np.arange(12).reshape(3, 4))
>>> (2 * A).array
array([[ 0, 2, 4, 6],
[ 8, 10, 12, 14],
[16, 18, 20, 22]])
As in the docs, the __r***__
are called to implement the binary arithmetic operations with reflected (swapped) operands. These functions are only called if the left operand does not support the corresponding operation and the operands are of different types.

Categories

Resources