A slightly long question to sufficiently explain the background...
Assuming there's a builtin class A:
class A:
def __init__(self, a=None):
self.a = a
def __eq__(self, other):
return self.a == other.a
It's expected to compare in this way:
a1, a2 = A(1), A(2)
a1 == a2 # False
For some reason, the team introduced a wrapper on top of it (The code example doesn't actually wrap A to simplify the code complexity.)
class WrapperA:
def __init__(self, a=None):
self.pa = a
def __eq__(self, other):
return self.pa == other.pa
Again, it's expected to compare in this way:
wa1, wa2 = WrapperA(1), WrapperA(2)
wa1 == wa2 # False
Although it's expected to use either A or WrapperA, the problem is some code bases contain both usages, thus following comparison failed:
a, wa = A(), WrapperA()
wa == a # AttributeError
a == wa # AttributeError
A known solution is to modify __eq__:
For wa == a:
class WrapperA:
def __init__(self, a=None):
self.pa = a
def __eq__(self, other):
if isinstance(other, A):
return self.pa == other.a
return self.pa == other.pa
For a == wa:
class A:
def __init__(self, a=None):
self.a = a
def __eq__(self, other):
if isinstance(other, WrapperA):
return self.a == other.pa
return self.a == other.a
Modifying WrapperA is expected. For A, since it is a builtin thing, two solutions are:
Use setattr to extend A to support WrapperA.
setattr(A, '__eq__', eq_that_supports_WrapperA)
Enforce developer to only compare wa == a (And then don't care about a == wa).
1st option is obviously ugly with duplicated implementation, and 2nd gives developer unnecessary "surprise". So my question is, is there an elegant way to replace any usage of a == wa to wa == a by the Python implementation internally?
Quoting the comment from MisterMiyagi under the question:
Note that == is generally expected to work across all types. A.__eq__ requiring other to be an A is actually a bug that should be fixed. It should at the very least return NotImplemented when it cannot make a decision
This is important, not just a question of style. In fact, according to the documentation:
When a binary (or in-place) method returns NotImplemented the interpreter will try the reflected operation on the other type.
Thus if you just apply MisterMiyagi's comment and fix the logic of __eq__, you'll see your code works fine already:
class A:
def __init__(self, a=None):
self.a = a
def __eq__(self, other):
if isinstance(other, A):
return self.a == other.a
return NotImplemented
class WrapperA:
def __init__(self, a=None):
self.pa = a
def __eq__(self, other):
if isinstance(other, A):
return self.pa == other.a
elif isinstance(other, WrapperA):
return self.pa == other.pa
return NotImplemented
# Trying it
a = A(5)
wrap_a = WrapperA(5)
print(a == wrap_a)
print(wrap_a == a)
wrap_a.pa = 7
print(a == wrap_a)
print(wrap_a == a)
print(f'{wrap_a.pa=}')
Yields:
True
True
False
False
wrap_a.pa=7
Under the hood, a == wrap_a calls A.__eq__ first, which returns NotImplemented. Python then automatically tries WrapperA.__eq__ instead.
I dont really like this whole thing, since I think that wrapping a builtin and using different attribute names will lead to unexpected stuff, but anyway, this will work for you
import inspect
class A:
def __init__(self, a=None):
self.a = a
def __eq__(self, other):
return self.a == other.a
class WrapperA:
def __init__(self, a=None):
self.pa = a
def __eq__(self, other):
if isinstance(other, A):
return self.pa == other.a
return self.pa == other.pa
def __getattribute__(self, item):
# Figure out who tried to get the attribute
# If the item requested was 'a', check if A's __eq__ method called us,
# in that case return pa instead
caller = inspect.stack()[1]
if item == 'a' and getattr(caller, 'function') == '__eq__' and isinstance(caller.frame.f_locals.get('self'), A):
return super(WrapperA, self).__getattribute__('pa')
return super(WrapperA, self).__getattribute__(item)
a = A(5)
wrap_a = WrapperA(5)
print(a == wrap_a)
print(wrap_a == a)
wrap_a.pa = 7
print(a == wrap_a)
print(wrap_a == a)
print(f'{wrap_a.pa=}')
Output:
True
True
False
False
wrap_a.pa=7
Similar to Ron Serruyas answer:
This uses __getattr__ instead of __getattribute__, where the first one is only called if the second one raises an AttributeError or explicitly calls it (ref). This means if the wrapper does not implement __eq__ and the equality should only be performed on the underlying data structure (stored in objects of class A), a working example is given by:
class A(object):
def __init__(self, internal_data=None):
self._internal_data = internal_data
def __eq__(self, other):
return self._internal_data == other._internal_data
class WrapperA(object):
def __init__(self, a_object: A):
self._a = a_object
def __getattr__(self, attribute):
if attribute != '_a': # This is neccessary to prevent recursive calls
return getattr(self._a, attribute)
a1 = A(internal_data=1)
a2 = A(internal_data=2)
wa1 = WrapperA(a1)
wa2 = WrapperA(a2)
print(
a1 == a1,
a1 == a2,
wa1 == wa1,
a1 == wa1,
a2 == wa2,
wa1 == a1)
>>> True False True True True True
Related
I'd like to create a generalized __eq__() method for the following Class. Basically I'd like to be able to add another property (nick) without having to change __eq__()
I imagine I can do this somehow by iterating over dir() but I wonder if there is a way to create a comprehension that just delivers the properties.
class Person:
def __init__(self, first, last):
self.first=first
self.last=last
#property
def first(self):
assert(self._first != None)
return self._first
#first.setter
def first(self,fn):
assert(isinstance(fn,str))
self._first=fn
#property
def last(self):
assert(self._last != None)
return self._last
#last.setter
def last(self,ln):
assert(isinstance(ln,str))
self._last=ln
#property
def full(self):
return f'{self.first} {self.last}'
def __eq__(self, other):
return self.first==other.first and self.last==other.last
p = Person('Raymond', 'Salemi')
p2= Person('Ray', 'Salemi')
You could use __dict__ to check if everything is the same, which scales for all attributes:
If the objects are not matching types, I simply return False.
class Person:
def __init__(self, first, last, nick):
self.first = first
self.last = last
self.nick = nick
def __eq__(self, other):
return self.__dict__ == other.__dict__ if type(self) == type(other) else False
>>> p = Person('Ray', 'Salemi', 'Ray')
>>> p2= Person('Ray', 'Salemi', 'Ray')
>>> p3 = Person('Jared', 'Salemi', 'Jarbear')
>>> p == p2
True
>>> p3 == p2
False
>>> p == 1
False
You can get all the properties of a Class with a construct like this:
from itertools import chain
#classmethod
def _properties(cls):
type_dict = dict(chain.from_iterable(typ.__dict__.items() for typ in reversed(cls.mro())))
return {k for k, v in type_dict.items() if 'property' in str(v)}
The __eq__ would become something like this:
def __eq__(self, other):
properties = self._properties() & other._properties()
if other._properties() > properties and self._properties() > properties:
# types are not comparable
return False
try:
return all(getattr(self, prop) == getattr(other, prop) for prop in properties)
except AttributeError:
return False
The reason to work with the reversed(cls.mro()) is so something like this also works:
class Worker(Person):
#property
def wage(self):
return 0
p4 = Worker('Raymond', 'Salemi')
print(p4 == p3)
True
you can try to do this, it will also work if you want eq inside dict and set
def __eq__(self, other):
"""Overrides the default implementation"""
if isinstance(self, other.__class__):
return self.__hash__() == other.__hash__()
return NotImplemented
def __hash__(self):
"""Overrides the default implementation,
and set which fieds to use for hash generation
"""
__make_hash = [
self.first
]
return hash(tuple(sorted(list(filter(None, __make_hash)))))
<!-- language: lang-py -->
class File:
### Сlass initialization
def __init__(self, path, name, size, date):
self.path = path
self.name = name
self.size = size
self.date = date
def __eq__(self, other):
# if self.name == other.name and self.size == other.size and self.date == other.date:
if self.name == other.name and self.size == other.size:
# if self.size == other.size and self.date == other.date:
return True**
How change (eq) of class during script execution?
def __eq__(self, other):
# if self.name == other.name and self.size == other.size and self.date == other.date:
if self.name == other.name and self.size == other.size:
# if self.size == other.size and self.date == other.date:
return True
Different variants must be triggered when certain conditions occur
Well, this is certainly possible:
class Foo(object):
def __init__(self, x):
self.x = x
def __eq__(self, other):
return other.x == self.x
foo1 = Foo(1)
foo2 = Foo(2)
print (foo1 == foo2)
def new_eq(self, other):
return other.x - 1 == self.x
Foo.__eq__ = new_eq
print (foo1 == foo2)
Explanation:
__eq__ is an attribute of the class Foo, and it's a function bound to the class (a class method). You can set the __eq__ attribute to a new function to replace it. Note that because this is modifying the class, all instances see a change, including foo1 and foo2 that are already instantiated.
All that said, this is a pretty sketchy practice, especially for something like __eq__, so I want to say that this is probably not a good solution to your problem, but not knowing what that problem is, I'll just say that if I were to see this sort of thing in code, it would make me rather nervous.
Instead of swapping __eq__ out on the fly, why not use the conditions to determine which case to use when __eq__ is called?
class Foo:
def __eq__(self, other):
if (self._condition_1):
return self._eq_condition_1(other)
elif (self._condition_2):
return self._eq_condition_2(other)
else:
return self._eq_condition_default(other)
def _eq_condition_1(self, other):
return True
def _eq_condition_2(self, other):
return False
def _eq_condition_default(self, other):
return True
When reading about how to implement __eq__ in python, such as in this SO question, you get recommendations like
class A(object):
def __init__(self, a, b):
self._a = a
self._b = b
def __eq__(self, other):
return (self._a, self._b) == (other._a, other._b)
Now, I'm having problems when combining this with inheritance. Specifically, if I define a new class
class B(A):
def new_method(self):
return self._a + self._b
Then I get this issue
>>> a = A(1, 2)
>>> b = B(1, 2)
>>> a == b
True
But clearly, a and b are not the (exact) same!
What is the correct way to implement __eq__ with inheritance?
If you mean that instances of different (sub)classes should not be equal, consider comparing their types as well:
def __eq__(self, other):
return (self._a, self._b, type(self)) == (other._a, other._b, type(other))
In this way A(1, 2) == B(1,2) returns False.
When performing a comparison between objects of two types where one type is derived from the other, Python ensures that the operator method is called on the derived class (in this case, B).
So to ensure that B objects compare dissimilarly to A objects, you can define __eq__ in B:
class B(A):
def __eq__(self, other):
return isinstance(other, B) and super(B, self).__eq__(other)
I want to create a check on inputs of a particular class, I have the following made up example:
class NmbPair:
def __init__(self, a = None, b = None):
self.a = a
self.b = b
def __eq__(self, other):
if self.a == other.a and self.b == other.b:
return True
return False
class NmbOperation:
def __init__(self, *, NmbPair1, NmbPair2):
if not self.check(NmbPair1, NmbPair2): ## this is the check
return
self.NmbPair1 = NmbPair1
self.NmbPair2 = NmbPair2
self._add_first_nmb()
def check(self, a, b):
if a == b:
return False
def _add_first_nmb(self):
self.sum_a = self.NmbPair1.a + self.NmbPair2.a
so i want to check that the input NmbPairs are not the same, and if they are I do not want an instance of NmbOperation to be created.
For example:
t1 = NmbPair(2, 3)
t2 = NmbPair(2, 2)
Op1 = NmbOperation(NmbPair1 = t1, NmbPair2 = t2)
print(Op1.sum_a)
But this throws the error:
AttributeError: 'NmbOperation' object has no attribute 'sum_a'
I'm not quite sure what I'm doing wrong
You are creating a NmbOperation object for which the __init__ method immediately returns before executing the lines
self.NmbPair1 = NmbPair1
self.NmbPair2 = NmbPair2
self._add_first_nmb()
This is because self.check(NmbPair1, NmbPair2) returns None, so not self.check(NmbPair1, NmbPair2) is True.
Therefore, the attribute sum_a is never set since _add_first_nmb is never called.
Your check method is equivalent to:
def check(self, a, b):
if a == b:
return False
else:
return None
You probably want
def check(self, a, b):
return not a == b
I have a tuple of python objects, from which I need a list of objects with no duplicates, using set() (this check for duplicate objects is to be done on an attribute.). This code will give a simple illustration:
class test:
def __init__(self, t):
self.t = t
def __repr__(self):
return repr(self.t)
def __hash__(self):
return self.t
l = (test(1), test(2), test(-1), test(1), test(3), test(2))
print l
print set(l)
However, it did not work. I can do it on an iteration over l, but any idea why set() is not working? Here is the official documentation.
From the documentation you linked to:
The set classes are implemented using dictionaries. Accordingly, the
requirements for set elements are the same as those for dictionary
keys; namely, that the element defines both __eq__() and __hash__().
To be more specific, if a == b then your implementation must be such that hash(a) == hash(b). The reverse is not required.
Also, you should probably call hash in __hash__ to handle long integers
class Test:
def __init__(self, t):
self.t = t
def __repr__(self):
return repr(self.t)
def __hash__(self):
return hash(self.t)
def __eq__(self, other):
return isinstance(other, Test) and self.t == other.t
Small nit picks:
Your implementation of __eq__ doesn't give the other object a chance to run its own __eq__. The class must also consider its members as immutable as the hash must stay constant. You don't want to break your dicts, do you?
class Test:
def __init__(self, t):
self._t = t
#property
def t(self):
return self._t
def __repr__(self):
return repr(self._t)
def __hash__(self):
return hash(self._t)
def __eq__(self, other):
if not isinstance(other, Test):
return NotImplemented # don't know how to handle `other`
return self.t == other.t