Magic methods with > 1 operands between MyClass and standard Class - python

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.

Related

How the python deal with add,sub,mul,div? [duplicate]

I have a vector class:
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __str__(self):
return '(%s,%s)' % (self.x, self.y)
def __add__(self, n):
if isinstance(n, (int, long, float)):
return Vector(self.x+n, self.y+n)
elif isinstance(n, Vector):
return Vector(self.x+n.x, self.y+n.y)
which works fine, i.e. I can write:
a = Vector(1,2)
print(a + 1) # prints (2,3)
However if the order of operation is reversed, then it fails:
a = Vector(1,2)
print(1 + a) # raises TypeError: unsupported operand type(s)
# for +: 'int' and 'instance'
I understand the error: the addition of an int object to an Vector object is undefined because I haven't defined it in the int class. Is there a way to work around this without defining it in the int (or parent of int) class?
You need to also define __radd__
Some operations do not necessarily evaluate like this a + b == b + a and that's why Python defines the add and radd methods.
Explaining myself better: it supports the fact that "int" does not define a + operation with class Vector instances as part of the operation. Therefore vector + 1 is not the same as 1 + vector.
When Python tries to see what the 1.__add__ method can do, an exception is raised. And Python goes and looks for Vector.__radd__ operation to try to complete it.
In the OP's case the evaluation is true and suffices with __radd__ = __add__
class Vector(object):
def __init__(self, x, y):
self.x, self.y = x, y
def __str__(self):
return '(%s,%s)' % (self.x, self.y)
def __add__(self, n):
if isinstance(n, (int, long, float)):
return Vector(self.x+n, self.y+n)
elif isinstance(n, Vector):
return Vector(self.x+n.x, self.y+n.y)
__radd__ = __add__
a = Vector(1, 2)
print(1 + a)
Which outputs:
(2,3)
The same applies to all number-like operations.
When you say x + y, Python calls x.__add__(y). If x does not implement __add__ (or that method returns NotImplemented), Python tries to call y.__radd__(x) as a fallback.
Thus all you have to do is to define the __radd__() method in your Vector class and 1 + y will work as you would expect.
Note: you would have to do similar for other operations too, e.g. implement __mul__() and __rmul__() pair, etc.
You might also want to look at this question, it explains the same principle in more details.
Update:
Depending on your use case, you might also want to implement the __iadd__() method (and its cousins) to override the += operator.
For example, if you say y += 1 (y being an instance of Vector here), you might want to modify the y instance itself, and not return a new Vector instance as a result, which what your __add__() method currently does.

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

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).

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.

Overriding Commutative Operations For Same Class Operands in Python

I am trying to understand how operator overriding works for two operands of a custom class.
For instance, suppose I have the following:
class Adder:
def __init__(self, value=1):
self.data = value
def __add__(self,other):
print('using __add__()')
return self.data + other
def __radd__(self,other):
print('using __radd__()')
return other + self.data
I initialize the following variables:
x = Adder(5)
y = Adder(4)
And then proceed to do the following operations:
1 + x
using __radd__()
Out[108]: 6
x + 2
using __add__()
Out[109]: 7
The two operations above seem straigtforward. If a member of my custom class is to the right of the "+" in the addition expression, then __radd__ is used. If it is on the left, then __add__ is used. This works for expressions when one operand is of the Adder type and another one is something else.
When I do this, however, I get the following result:
x + y
using __add__()
using __radd__()
Out[110]: 9
As you can see, if both operands are of the custom class, then both __add__ and __radd__ are called.
My question is how does Python unravel this situation and how is it able to call both the right-hand-addition function, as well as the left-hand-addition function.
It's because inside your methods you add the data to other. This is itself an instance of Adder. So the logic goes:
call __add__ on x;
add x.data (an int) to y (an Adder instance)
ah, right-hand operand is an instance with a __radd__ method, so
call __radd__ on y;
add int to y.data (another int).
Usually you would check to see if other was an instance of your class, and if so add other.data rather than just other.
That's the because the implementation of your __add__ and __radd__ method do not give any special treatment to the instances of the Adder class. Therefore, each __add__ call leads to an integer plus Adder instance operation which further requires __radd__ due to the Adder instance on the right side.
You can resolve this by doing:
def __add__(self, other):
print('using __add__()')
if isinstance(other, Adder):
other = other.data
return self.data + other
def __radd__(self, other):
print('using __radd__()')
return self.__add__(other)

Categories

Resources