Python set intersection and __eq__ - python

According to this page, set.intersection test for element equality using the __eq__ method. Can anyone then explain to me why this fails?
>>> Class Foo(object):
>>> def __eq__(self, other):
>>> return True
>>>
>>> set([Foo()]).intersection([Foo()])
set([])
Using 2.7.3. Is there another (not overly complex) way to do this?

If you overwrite __eq__ you should always overwrite __hash__, too.
"If a == b, then it must be the case that hash(a) == hash(b), else sets
and dictionaries will fail." Eric
__hash__ is used to generate an integer out of an object.
This is used to put the keys of a dict or the elements of sets into buckets so that one can faster find them.
If you do not overwrite __hash__, the default algorithm creates different hash-integers although the objects are equal.
In your case I would do this:
class Foo(object):
def __eq__(self, other):
return type(self) == type(other)
def __hash__(self):
return 1
Because all objects of your class are equal to every other object of that class they must all be in the same bucket(1) in the set. This way in returns also True.
What should __eq__ be like:
if you only compare Foo objects
def __eq__(self, other):
return self.number == other.number
if you also compare Foo objects to other objects:
def __eq__(self, other):
return type(self) == type(other) and self.number == other.number
if you have different classes with different algorithms for equal, I recommend double-dispatch.
class Foo:
def __eq__(self, other):
return hasattr(other, '_equals_foo') and other._equals_foo(self)
def _equals_foo(self, other):
return self.number == other.number
def _equals_bar(self, other):
return False # Foo never equals Bar
class Bar:
def __eq__(self, other):
return hasattr(other, '_equals_bar') and other._equals_bar(self)
def _equals_foo(self, other):
return False # Foo never equals Bar
def _equals_bar(self, other):
return True # Bar always equals Bar
This way both a and b in a == b decide what equal means.

Related

Specifying "any instance of class Foo" for mock assert_called_once_with()?

In assert_called_once_with, how can I specify a parameter is "any instance of class Foo"?
For example:
class Foo(): pass
def f(x): pass
def g(): f(Foo())
import __main__
from unittest import mock
mock.ANY of course passes:
with mock.patch.object(__main__, 'f') as mock_f:
g()
mock_f.assert_called_once_with(mock.ANY)
and of course, another instance of Foo doesn't pass.
with mock.patch.object(__main__, 'f') as mock_f:
g()
mock_f.assert_called_once_with(Foo())
AssertionError: Expected call: f(<__main__.Foo object at 0x7fd38411d0b8>)
Actual call: f(<__main__.Foo object at 0x7fd384111f98>)
What can I put as my expected parameter such that any instance of Foo will make the assertion pass?
One simple solution is to do this in two steps:
with mock.patch.object(__main__, 'f') as mock_f:
g()
mock_f.assert_called_once()
self.assertIsInstance(mock_f.mock_calls[0].args[0], Foo)
However, if you look at the implementation of ANY:
class _ANY(object):
"A helper object that compares equal to everything."
def __eq__(self, other):
return True
def __ne__(self, other):
return False
def __repr__(self):
return '<ANY>'
ANY = _ANY()
you can see it's just an object that's equal to anything. So you could define your own equivalent that's equal to any instance of Foo:
class AnyFoo:
"A helper object that compares equal to every instance of Foo."
def __eq__(self, other):
return isinstance(other, Foo)
def __ne__(self, other):
return not isinstance(other, Foo)
def __repr__(self):
return '<ANY Foo>'
ANY_FOO = AnyFoo()
Or more generically:
class AnyInstanceOf:
"A helper object that compares equal to every instance of the specified class."
def __init__(self, cls):
self.cls = cls
def __eq__(self, other):
return isinstance(other, self.cls)
def __ne__(self, other):
return not isinstance(other, self.cls)
def __repr__(self):
return f"<ANY {self.cls.__name__}>"
ANY_FOO = AnyInstanceOf(Foo)
Either way, you can use it as you would ANY:
with mock.patch.object(__main__, 'f') as mock_f:
g()
mock_f.assert_called_once_with(ANY_FOO)

Single function comparison in custom Python classes

I often need to make a class I made comparable for various reasons (sorting, set usage,...) and I don't want to write multiple comparison functions each time. How do I support the class so I only have to write a single function for every new class?
My solution to the problem is to create an abstract class a class can inherit and override the main comparison function (diff()) with the desired comparison method.
class Comparable:
'''
An abstract class that can be inherited to make a class comparable and sortable.
For proper functionality, function diff must be overridden.
'''
def diff(self, other):
"""
Calculates the difference in value between two objects and returns a number.
If the returned number is
- positive, the value of object a is greater than that of object b.
- 0, objects are equivalent in value.
- negative, value of object a is lesser than that of object b.
Used in comparison operations.
Override this function."""
return 0
def __eq__(self, other):
return self.diff(other) == 0
def __ne__(self, other):
return self.diff(other) != 0
def __lt__(self, other):
return self.diff(other) < 0
def __le__(self, other):
return self.diff(other) <= 0
def __gt__(self, other):
return self.diff(other) > 0
def __ge__(self, other):
return self.diff(other) >= 0

Equality and inheritance in python

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)

Comparison of hashable objects

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

How does a Python set([]) check if two objects are equal? What methods does an object need to define to customise this?

I need to create a 'container' object or class in Python, which keeps a record of other objects which I also define. One requirement of this container is that if two objects are deemed to be identical, one (either one) is removed. My first thought was to use a set([]) as the containing object, to complete this requirement.
However, the set does not remove one of the two identical object instances. What must I define to create one?
Here is the Python code.
class Item(object):
def __init__(self, foo, bar):
self.foo = foo
self.bar = bar
def __repr__(self):
return "Item(%s, %s)" % (self.foo, self.bar)
def __eq__(self, other):
if isinstance(other, Item):
return ((self.foo == other.foo) and (self.bar == other.bar))
else:
return False
def __ne__(self, other):
return (not self.__eq__(other))
Interpreter
>>> set([Item(1,2), Item(1,2)])
set([Item(1, 2), Item(1, 2)])
It is clear that __eq__(), which is called by x == y, is not the method called by the set. What is called? What other method must I define?
Note: The Items must remain mutable, and can change, so I cannot provide a __hash__() method. If this is the only way of doing it, then I will rewrite for use of immutable Items.
Yes, you need a __hash__()-method AND the comparing-operator which you already provided.
class Item(object):
def __init__(self, foo, bar):
self.foo = foo
self.bar = bar
def __repr__(self):
return "Item(%s, %s)" % (self.foo, self.bar)
def __eq__(self, other):
if isinstance(other, Item):
return ((self.foo == other.foo) and (self.bar == other.bar))
else:
return False
def __ne__(self, other):
return (not self.__eq__(other))
def __hash__(self):
return hash(self.__repr__())
I am afraid you will have to provide a __hash__() method. But you can code it the way, that it does not depend on the mutable attributes of your Item.

Categories