This question already has answers here:
Compare object instances for equality by their attributes
(16 answers)
Closed 6 years ago.
What is the best practice for defining an equality operation to determine if a parent and child are effectively the same? Is there any good way to achieve this, or should I just avoid this desired behavior with a architecture change?
Parent Class
class Equal1(object):
def __init__(self, value):
self.value=value
def get_value(self):
return self.value
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.__dict__==other.__dict__
return NotImplemented
Child Class
class Equal2(Equal1):
def __init__(self, value):
super().__init__(2 * value)
self.other_values = ''
I want the following code to evaluate to True:
a = Equal1(2)
b = Equal2(1)
a == b
## True
It currently will not evaluate to True because the dictionaries are not identical, even if the functionality is the same.
One potential technique is to iterate through the smaller dictionary to check all of the key/value pairs to make sure they are the same as well as checking the local attributes using dir().
So you want to ignore the keys that only one of the objects has?
I would suggest replacing:
return self.__dict__==other.__dict__
with:
return all(self[k] == other[k]
for k in self.__dict__.keys() & other.__dict__.keys())
This makes the objects equal if all the keys, they both have, have equal values.
Related
I had a question about dictionaries with custom objects. In a dict, I know that the key has to be immutable, so if I want to use a custom class, I have to define its hash function. The hash doc in python recommends you use the hash function on the tuple of the equal dunder method. So for example, i defined the custom class temp as such:
class temp():
def __init__(self,value):
self.value = value
def __hash__(self):
return hash(self.value)
def __eq__(self,other):
return self.value == other.value
def __lt__(self,other):
return self.value < other.value
This way I can have they key:value pair such as temp(1):1. So to my question. In python, you can have different types in the same dict. So I declared this dict:
myDict={ temp(1):1, temp(2):2, 'a':1,1:1, (1,2):1, True:1 }
The problem I am facing is that I would get an error for the int:int and bool:int pairing telling me the error:
'bool' object has no attribute 'value'
or
'int' object has no attribute 'value'
Can someone explain to me why this is the case? The same issue would happen if I have a different class in the dict as well. So an object from a cars class would give this error:
'cars' object has no attribute 'value'
Strangely enough in my tests, I found that if the key is a tuple or a float, it works fine.
Any help would be greatly appreciated. I wanted to know why the error is happening and how I can fix it. MY main goal is to learn how to my one dict that has various objects from different classes.
Your eq method needs to check if the other object is the same type:
def __eq__(self,other):
if not isinstance(other, temp):
return NotImplemented
return self.value==other.value
That said, I highly recommend using dataclasses for cases like this. They define init, eq, and (if frozen=True) hash for you, which helps avoid this sort of issue.
You can define your __eq__ method like this:
def __eq__(self, other):
if other is None:
return False
if self.__class__ != other.__class__:
return False
return self.value == other.value
Strangely enough in my tests, I found that if the key is a tuple or a float, it works fine.
As for the second question, this has got to do with how a dict works.
For every key, the instance of dict checks if the hash of the key exists. If yes, then it checks for equality with other keys with the same hash. Here, the check for equality is to check if they are basically the same keys (and hence the same hash). If the equality check fails, then the keys are deemed different.
If there are no hash collisions, then no equality checks are done. Hence, when you used a tuple as a key, say, (1, 2), its hash((1, 2)) = 3713081631934410656, which doesn't yet exist in the dict. Hence no error.
The issue happens when running the __eq__ and __lt__ dunder methods. You can reproduce the same by running:
temp(1) == 1
The issue happens because __eq__ receives other as 1, and the value 1 does not have a .value, but you're trying to use it here:
return self.value == other.value
If you just use other for comparisons it should work:
class temp():
def __init__(self,value):
self.value = value
def __hash__(self):
return hash(self.value)
def __eq__(self,other):
return self.value == other
def __lt__(self,other):
return self.value < other
I wanted to store instances of a class in a set, so I could use the set methods to find intersections, etc. My class has a __hash__() function, along with an __eq__ and a __lt__, and is decorated with functools.total_ordering
When I create two sets, each containing the same two objects, and do a set_a.difference(set_b), I get a result with a single object, and I have no idea why. I was expecting none, or at the least, 2, indicating a complete failure in my understanding of how sets work. But one?
for a in set_a:
print(a, a.__hash__())
for b in set_b:
print(b, b.__hash__(), b in set_a)
(<foo>, -5267863171333807568)
(<bar>, -8020339072063373731)
(<foo>, -5267863171333807568, False)
(<bar)>, -8020339072063373731, True)
Why is the <foo> object in set_b not considered to be in set_a? What other properties does an object require in order to be considered a member of a set? And why is bar considered to be a part of set_a, but not foo?
edit: updating with some more info. I figured that simply showing that the two objects' hash() results where the same meant that they where indeed the same, so I guess that's where my mistake probably comes from.
#total_ordering
class Thing(object):
def __init__(self, i):
self.i = i
def __eq__(self, other):
return self.i == other.i
def __lt__(self, other):
return self.i < other.i
def __repr__(self):
return "<Thing {}>".format(self.i)
def __hash__(self):
return hash(self.i)
I figured it out thanks to some of the questions in the comments- the problem was due to the fact that I had believed that ultimately, the hash function decides if two objects are the same, or not. The __eq__ also needs to match, which it always did in my tests and attempts to create a minimal example here.
However, when pulling data from a DB in prod, a certain float was being rounded down, and thus, the x == y was failing in prod. Argh.
I have an object that I'd like to be able to make equality comparisons on where it looks at all of the objects data to make that comparison. I'd also like to sort the objects based on only one of the object's properties.
Here's an example:
#total_ordering
class Msg():
def __init__(self, value="", timestamp=None):
self.value = value
self.timestamp = timestamp
def is_equal(self, other):
""" 2 messages are equal if they contain all the same values """
return (
self.value == other.value
and self.timestamp == other.timestamp
)
def __eq__(self, other):
return self.timestamp == other.timestamp
def __lt__(self, other):
return self.timestamp < other.timestamp
So, with this example I can easily do sorted(msgs) and get back an ordered list. However, because I'm using the __eq__ to deal with the sorting, I can't do a simple msg1 == msg2 comparison when I want to test if all of the properties are the same. If I change the __eq__ to deal with the equality case, though, will my sort continue to function properly? In that case it's possible that not msg1 < msg2 and not msg1 > msg2 and msg1 != msg2... will that not confuse the sort method?
I guess I can use the "key" argument on sorted, but as I have only one way I'd like to sort these objects I'd like to incorporate an automatic way for it to sort them properly.
It'd be nice if there was a __key__ magic operator that sorted called.
This question already has answers here:
What is memoization and how can I use it in Python?
(14 answers)
Closed 9 years ago.
Suppose that I have class A and this class has a method called function. Can I assign a cache as a property to this method? In the sense that I could call it like a property?
class A:
def __init__(self,value):
self.value=value
def function(self,a):
"""function returns a+1 and caches the value for future calls."""
cache=[]
cache.append([a,a+1])
return a+1;
a=A(12)
print a.function(12)
print a.function.cache
Which gives me the error:
AttributeError: 'function' object has no attribute 'cache'
I know it is possible to assign a cache to the main class but I am looking for a possible way of assigning it as a property to the method object.
class A:
def __init__(self,value):
self.value=value
self.cache = {}
def function(self,a):
"""function returns a+1 and caches the value for future calls."""
# Add a default value of empty string to avoid key errors,
# check if we already have the value cached
if self.cache.get(a,''):
return self.cache[a]
else:
result = a + 1
self.cache[a] = result
return result
As far as I know there is no way of having the cache as a property of the method. Python doesn't have such a feature. But I think perhaps this solution will satisfy your needs.
EDIT
Upon further research, there is indeed a way to do this in Python 3
class A:
def __init__(self,value):
self.value=value
def function(self,a):
"""function returns a+1 and caches the value for future calls."""
# Add a default value of empty string to avoid key errors,
# check if we already have the value cached
if self.function.cache.get(a,''):
return self.function.cache[a]
else:
result = a + 1
self.function.cache[a] = result
return result
function.cache = {}
a=A(12)
print(a.function(12))
print(a.function.cache)
This is because in Python 3 instance methods are just functions. BTW in Python 2 it is indeed possible to add attributes to functions, but not to instance methods. If you need to use Python 2 then there is a solution to your problem involving decorators that you should look into.
I have a set of objects, and am interested in getting a specific object from the set. After some research, I decided to use the solution provided here: http://code.activestate.com/recipes/499299/
The problem is that it doesn't appear to be working.
I have two classes defined as such:
class Foo(object):
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
def __key(self):
return (self.a, self.b, self.c)
def __eq__(self, other):
return self.__key() == other.__key()
def __hash__(self):
return hash(self.__key())
class Bar(Foo):
def __init__(self, a, b, c, d, e):
self.a = a
self.b = b
self.c = c
self.d = d
self.e = e
Note: equality of these two classes should only be defined on the attributes a, b, c.
The wrapper _CaptureEq in http://code.activestate.com/recipes/499299/ also defines its own __eq__ method. The problem is that this method never gets called (I think). Consider,
bar_1 = Bar(1,2,3,4,5)
bar_2 = Bar(1,2,3,10,11)
summary = set((bar_1,))
assert(bar_1 == bar_2)
bar_equiv = get_equivalent(summary, bar_2)
bar_equiv.d should equal 4 and likewise bar_equiv .e should equal 5, but they are not. Like I mentioned, it looks like the __CaptureEq __eq__ method does not get called when the statement bar_2 in summary is executed.
Is there some reason why the __CaptureEq __eq__ method is not being called? Hopefully this is not too obscure of a question.
Brandon's answer is informative, but incorrect. There are actually two problems, one with
the recipe relying on _CaptureEq being written as an old-style class (so it won't work properly if you try it on Python 3 with a hash-based container), and one with your own Foo.__eq__ definition claiming definitively that the two objects are not equal when it should be saying "I don't know, ask the other object if we're equal".
The recipe problem is trivial to fix: just define __hash__ on the comparison wrapper class:
class _CaptureEq:
'Object wrapper that remembers "other" for successful equality tests.'
def __init__(self, obj):
self.obj = obj
self.match = obj
# If running on Python 3, this will be a new-style class, and
# new-style classes must delegate hash explicitly in order to populate
# the underlying special method slot correctly.
# On Python 2, it will be an old-style class, so the explicit delegation
# isn't needed (__getattr__ will cover it), but it also won't do any harm.
def __hash__(self):
return hash(self.obj)
def __eq__(self, other):
result = (self.obj == other)
if result:
self.match = other
return result
def __getattr__(self, name): # support anything else needed by __contains__
return getattr(self.obj, name)
The problem with your own __eq__ definition is also easy to fix: return NotImplemented when appropriate so you aren't claiming to provide a definitive answer for comparisons with unknown objects:
class Foo(object):
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
def __key(self):
return (self.a, self.b, self.c)
def __eq__(self, other):
if not isinstance(other, Foo):
# Don't recognise "other", so let *it* decide if we're equal
return NotImplemented
return self.__key() == other.__key()
def __hash__(self):
return hash(self.__key())
With those two fixes, you will find that Raymond's get_equivalent recipe works exactly as it should:
>>> from capture_eq import *
>>> bar_1 = Bar(1,2,3,4,5)
>>> bar_2 = Bar(1,2,3,10,11)
>>> summary = set((bar_1,))
>>> assert(bar_1 == bar_2)
>>> bar_equiv = get_equivalent(summary, bar_2)
>>> bar_equiv.d
4
>>> bar_equiv.e
5
Update: Clarified that the explicit __hash__ override is only needed in order to correctly handle the Python 3 case.
The problem is that the set compares two objects the “wrong way around” for this pattern to intercept the call to __eq__(). The recipe from 2006 evidently was written against containers that, when asked if x was present, went through the candidate y values already present in the container doing:
x == y
comparisons, in which case an __eq__() on x could do special actions during the search. But the set object is doing the comparison the other way around:
y == x
for each y in the set. Therefore this pattern might simply not be usable in this form when your data type is a set. You can confirm this by instrumenting Foo.__eq__() like this:
def __eq__(self, other):
print '__eq__: I am', self.d, self.e, 'and he is', other.d, other.e
return self.__key() == other.__key()
You will then see a message like:
__eq__: I am 4 5 and he is 10 11
confirming that the equality comparison is posing the equality question to the object already in the set — which is, alas, not the object wrapped with Hettinger's _CaptureEq object.
Update:
And I forgot to suggest a way forward: have you thought about using a dictionary? Since you have an idea here of a key that is a subset of the data inside the object, you might find that splitting out the idea of the key from the idea of the object itself might alleviate the need to attempt this kind of convoluted object interception. Just write a new function that, given an object and your dictionary, computes the key and looks in the dictionary and returns the object already in the dictionary if the key is present else inserts the new object at the key.
Update 2: well, look at that — Nick's answer uses a NotImplemented in one direction to force the set to do the comparison in the other direction. Give the guy a few +1's!
There are two issues here. The first is that:
t = _CaptureEq(item)
if t in container:
return t.match
return default
Doesn't do what you think. In particular, t will never be in container, since _CaptureEq doesn't define __hash__. This becomes more obvious in Python 3, since it will point this out to you rather than providing a default __hash__. The code for _CaptureEq seems to believe that providing an __getattr__ will solve this - it won't, since Python's special method lookups are not guaranteed to go through all the same steps as normal attribute lookups - this is the same reason __hash__ (and various others) need to be defined on a class and can't be monkeypatched onto an instance. So, the most direct way around this is to define _CaptureEq.__hash__ like so:
def __hash__(self):
return hash(self.obj)
But that still isn't guaranteed to work, because of the second issue: set lookup is not guaranteed to test equality. sets are based on hashtables, and only do an equality test if there's more than one item in a hash bucket. You can't (and don't want to) force items that hash differently into the same bucket, since that's all an implementation detail of set. The easiest way around this issue, and to neatly sidestep the first one, is to use a list instead:
summary = [bar_1]
assert(bar_1 == bar_2)
bar_equiv = get_equivalent(summary, bar_2)
assert(bar_equiv is bar_1)