Having some trouble understanding why I'm able to re-define (monkey patch) __eq__ outside of a class, but not change its definition through __init__ or in a method:
class SpecialInteger:
def __init__(self,x):
self.x = x
self.__eq__ = self.equals_normal
def equals_normal(self,other):
return self.x == other.x
def equals_special(self,other):
return self.x != other.x
def switch_to_normal(self):
self.__eq__ = self.equals_normal
def switch_to_special(self):
self.__eq__ = self.equals_special
a = SpecialInteger(3)
b = SpecialInteger(3)
print(a == b) # false
a.switch_to_normal()
print(a == b) # false
SpecialInteger.__eq__ = SpecialInteger.equals_normal
print(a == b) # true
SpecialInteger.__eq__ = SpecialInteger.equals_special
print(a == b) # false
Am I just using self incorrectly or is there some other reason it works like this?
To do it inside the class, you would simply define the __eq__ method inside of your class.
class SpecialInteger:
def __init__(self,x):
self.x = x
def __eq__(self, other):
# do stuff, call whatever other methods you want
EDIT: I see what you are asking, you wish to override the method (which is a "magic" method) at the instance level. I don't believe this is possible in the base construct of the language, per this discussion.
The reason your monkey patch works in that example is because it is being passed on the Class level, as opposed to the instance level, whereas self is referring to the instance.
Just to add on to an excellent existing answer, but this doesn't work because you are modifying the class instance, and not the class.
In order to get the behavior you desire, you can modify the class during __init__, however, this is woefully inadequate (since it modifies the class, and therefore all instances of the class), and you are better off making those changes visible at the class scope.
For example, the following are equivalent:
class SpecialInteger1:
def __init__(self,x):
self.x = x
self.__class__.__eq__ = self.equals_normal
...
class SpecialInteger2:
def __init__(self,x):
self.x = x
def equals_normal(self,other):
return self.x == other.x
def __eq__(self, other):
return self.equals_normal(other)
You should prefer case SpecialInteger2 in all examples, since it is more explicit about what it does.
However, none of this actually solves the issue you are trying to solve: how can I create a specialized equality comparison at the instance level that I can toggle? The answer is through the use of an enum (in Python 3):
from enum import Enum
class Equality(Enum):
NORMAL = 1
SPECIAL = 2
class SpecialInteger:
def __init__(self, x, eq = Equality.NORMAL):
self.x = x
self.eq = eq
def equals_normal(self, other):
return self.x == other.x
def equals_special(self, other):
return self.x != other.x
def __eq__(self, other):
return self.__comp[self.eq](self, other)
# Define a dictionary for O(1) access
# to call the right method.
__comp = {
Equality.NORMAL: equals_normal,
Equality.SPECIAL: equals_special
}
Let's walk through this quickly, since there are 3 parts:
eq, which can be modified dynamically.__eq__ that selects the correct equality function based on the value of self.eq.__, in this case, self.__comp) that allows efficient lookup of the desired equality method.The dictionary can easily be done-away with, especially for cases where you only wish to support 1-5 different possible comparisons, and replaced with idiomatic if/then statements, however, if you ever wish to support many more comparison options (say, 300), a dictionary will be much more efficient O(1) than if/then comparisons (linear search, O(n)).
If you wish to do this with setters (like in the original example), and actually hide the member functions from the user, you can also do this by directly storing the function as a variable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With