You have a Python class which needs an equals test. Python should use duck-typing but is it (better/more accurate) to include or exclude an isinstance test in the eq function? For example:
class Trout(object):
def __init__(self, value):
self.value = value
def __eq__(self, other):
return isinstance(other, Trout) and self.value == other.value
Using isinstance in __eq__ methods is pretty common. The reason for this is that if the __eq__ method fails, it can fallback on an __eq__ method from another object. Most normal methods are called explicitly, but __eq__ is called implicitly, so it requires look-before-you-leap more frequently.
EDIT (thanks for the reminder, Sven Marnach):
To make it fallback, you can return the NotImplemented singleton, as in this example:
class Trout(object):
def __init__(self, value):
self.value = value
def __eq__(self, other):
if isinstance(other, Trout):
return self.value == other.value
else:
return NotImplemented
Suppose a RainbowTrout knows how to compare itself to a Trout or to another RainbowTrout, but a Trout only knows how to compare itself to a Trout. In this example, if you test mytrout == myrainbowtrout, Python will first call mytrout.__eq__(myrainbowtrout), notice that it fails, and then call myrainbowtrout.__eq__(mytrout), which succeeds.
Using isintsance() is usually fine in __eq__() methods. You shouldn't return False immediately if the isinstance() check fails, though -- it is better to return NotImplemented to give other.__eq__() a chance of being executed:
def __eq__(self, other):
if isinstance(other, Trout):
return self.x == other.x
return NotImplemented
This will become particularly important in class hierarchies where more than one class defines __eq__():
class A(object):
def __init__(self, x):
self.x = x
def __eq__(self, other):
if isinstance(other, A):
return self.x == other.x
return NotImplemented
class B(A):
def __init__(self, x, y):
A.__init__(self, x)
self.y = y
def __eq__(self, other):
if isinstance(other, B):
return self.x, self.y == other.x, other.y
return NotImplemented
If you would return False immediately, as you did in your original code, you would lose symmetry between A(3) == B(3, 4) and B(3, 4) == A(3).
The "duck-typing" principle is that you don't care what other is, as long as it has a value attribute. So unless your attributes share names with conflicting semantics, I'd suggest doing it like this:
def __eq__(self, other):
try:
return self.value == other.value
except AttributeError:
return False # or whatever
(Alternately you could test whether other has a value attribute, but "it's easier to ask forgiveness than to get permission")
Related
I am having trouble understanding how isinstance is meant to work with the Abstract Base Classes from collections.abc. My class that implements all specified methods for collections.abc.Mapping does not make isinstance return True for my class, but does make isinstance return True for collections.abc.Collection. My class is not registered as a subclass with either ABC.
Running the following code (with Python 3.7, but I'm not sure if that matters):
class dictroproxy:
def __init__(self, d):
self._d = d
def __getitem__(self, key):
return self._d.__getitem__(key)
def __contains__(self, key):
return self._d.__contains__(key)
def __len__(self):
return self._d.__len__()
def __iter__(self):
return self._d.__iter__()
def __eq__(self, other):
if isinstance(other, dictroproxy):
other = other._d
return self._d.__eq__(other)
def __ne__(self, other):
return not self.__eq__(other)
def get(self, key, default=None):
return self._d.get(key, default)
def keys(self):
return self._d.keys()
def values(self):
return self._d.values()
def items(self):
return self._d.items()
if __name__ == "__main__":
from collections.abc import Collection, Mapping
dd = dictroproxy({"a": 1, "b": 2, "c": 3})
print("Is collection?", isinstance(dd, Collection))
print("Is mapping?", isinstance(dd, Mapping))
Gives me the following output:
Is collection? True
Is mapping? False
Am I missing something in my implementation, or do Collection and Mapping behave differently?
From my investigation into this I found that some ABCs implement a __subclasshook__ method to determine if a class seems like a subclass of the ABC. At least as of Python 3.9, some ABCs implement __subclasshook__ and some do not. Collection does and Mapping does not.
I haven't seen which ABCs this works for documented anywhere, so it may be that the only way to know is to try or review the source code.
I have looked for an explicit answer, but the closest result was in the python docs here. It says:
These [augmented arithmetic assignments] methods should attempt to do the operation in-place (modifying self) and return the result (which could be, but does not have to be, self).
This implies the self (and the type) are unchanged, but is really up to the class developer to determine or should an attempt be make to always maintain the type?
I'm aware that should is an opinion question. What I'm looking for is better documented evidence on expected result.
Take the class NumString below.
class NumString:
"""A class where the number is represented as a string.
other methods omitted for brevity.
"""
def __init__(self, value):
self.value = str(value)
def __str__(self):
return self.value
def __int__(self):
return int(self.value))
def __add__(self, other):
if '.' in self.value:
return float(self) + other
return int(self) + other
def __iadd__(self, other):
self.value = str(self + other)
return self
I have a class in Python which is little more than the primitive values, like int or float, see below
class Entry:
def __init__(self, value, timestamp):
self.value = value
self.timestamp = timestamp
def __str__(self):
return"[v= {}, ts= {}]".format(self.value, self.timestamp)
def __hash__(self):
return hash(self.timestamp)
def __eq__(self, other):
return self.timestamp == other.timestamp
def __le__(self, other):
return self.timestamp <= other.timestamp
def __lt__(self, other):
return self.timestamp < other.timestamp
def __ge__(self, other):
return self.timestamp >= other.timestamp
def __gt__(self, other):
return self.timestamp > other.timestamp
def __copy__(self):
new_entry = Entry(deepcopy(self.value), self.timestamp)
print("hi")
return new_entry
e1 = Entry("some name", 10)
e2 = e1
e2.timestamp = 20
print(e1)
I want it to behave just like the primitive types as well. So when an assignment occurs, like above, the value is deep-copied, so I don't have to think about doing it manually everywhere I do assigment like this.
As you can see, I tried overriding the __copy__ method. Unfortunely that method isn't called here. Is there another method to override? I'm pretty sure this can be accomplished in C++. Can it be done in Python too?
You can't override the = assignment operator in Python, because it isn't a "copy" operator. Instead it binds an object to a value. You can, however, use the copy module, as described here: https://docs.python.org/3/library/copy.html.
I'm just trying to make an Enum in Python 3 by reference of the official Python docs https://docs.python.org/3.4/library/enum.html and specifically 8.13.13.2 and 8.13.13.4 examples.
My target is having an Enum which I can iterate, compare and also having three separate attributes. But I keep finding this error:
AttributeError: can't set attribute
It seems an error in __init__() constructor.
Code:
I tried firstly with one only class like this:
class Hand(Enum):
FIVE_OF_KIND = (6,'FIVE_OF_KIND',[5])
FOUR_OF_KIND = (5,'FOUR_OF_KIND',[4,1])
FULL_HOUSE = (4,'FULL_HOUSE',[3,2])
THREE_OF_KIND = (3,'THREE_OF_KIND',[3,1,1])
DOUBLE_PAIR = (2,'DOUBLE_PAIR',[2,2,1])
PAIR = (1,'PAIR',[2,1,1,1])
NOTHING = (0,'NOTHING',[1,1,1,1,1])
def __init__(self, val, name, struct):
self.val = val
self.name = name
self.struct = struct
def __ge__(self, other):
if self.__class__ is other.__class__:
return self.value >= other.value
return NotImplemented
def __gt__(self, other):
if self.__class__ is other.__class__:
return self.value > other.value
return NotImplemented
def __le__(self, other):
if self.__class__ is other.__class__:
return self.value <= other.value
return NotImplemented
def __lt__(self, other):
if self.__class__ is other.__class__:
return self.value < other.value
return NotImplemented
and secondly with two classes like this:
class OrderedEnum(Enum):
def __ge__(self, other):
if self.__class__ is other.__class__:
return self.value >= other.value
return NotImplemented
def __gt__(self, other):
if self.__class__ is other.__class__:
return self.value > other.value
return NotImplemented
def __le__(self, other):
if self.__class__ is other.__class__:
return self.value <= other.value
return NotImplemented
def __lt__(self, other):
if self.__class__ is other.__class__:
return self.value < other.value
return NotImplemented
class Hand(OrderedEnum):
FIVE_OF_KIND = (6,'FIVE_OF_KIND',[5])
FOUR_OF_KIND = (5,'FOUR_OF_KIND',[4,1])
FULL_HOUSE = (4,'FULL_HOUSE',[3,2])
THREE_OF_KIND = (3,'THREE_OF_KIND',[3,1,1])
DOUBLE_PAIR = (2,'DOUBLE_PAIR',[2,2,1])
PAIR = (1,'PAIR',[2,1,1,1])
NOTHING = (0,'NOTHING',[1,1,1,1,1])
def __init__(self, val, name, struct):
self.val = val
self.name = name
self.struct = struct
Enum objects already have a name attribute (for example, see 8.13.13.3), and apparently you are not allowed to set it – which makes sense when you think about how an enum should behave. You can achieve what you want like this:
from enum import Enum
class OrderedEnum(Enum):
# Same as your code.
class Hand(OrderedEnum):
FIVE_OF_KIND = (6, [5])
FOUR_OF_KIND = (5, [4,1])
FULL_HOUSE = (4, [3,2])
THREE_OF_KIND = (3, [3,1,1])
DOUBLE_PAIR = (2, [2,2,1])
PAIR = (1, [2,1,1,1])
NOTHING = (0, [1,1,1,1,1])
def __init__(self, val, struct):
# No need to set self.name. It's already handled.
self.val = val
self.struct = struct
for h in Hand:
print((h.name, h.val, h.struct))
I have the following custom classes (stripped down some), implementing an expression tree:
from abc import ABC, abstractmethod
class Operator:
def __init__(self, str_reps):
self.str_reps = str_reps
def __str__(self):
return self.str_reps[0]
def __eq__(self, other):
return self is other
def __ne__(self, other):
return self is not other
def __hash__(self):
return hash(str(self))
NOT = Operator(["¬", "~", "not", "!"])
AND = Operator(["∧", "&", "and"])
OR = Operator(["∨", "|", "or"])
class Node(ABC):
#abstractmethod
def __eq__(self, other):
pass
#abstractmethod
def __hash__(self):
pass
def __ne__(self, other):
return not self == other
#abstractmethod
def __str__(self):
pass
#abstractmethod
def __invert__(self):
pass
def bracket_if_necessary(self):
return str(self)
class Leaf(Node):
def __init__(self, v):
self.val = v
def __eq__(self, other):
if not isinstance(other, Leaf):
return False
return self.val == other.val
def __hash__(self):
return hash(self.val)
def __str__(self):
return str(self.val)
def __invert__(self):
return UnaryNode(self)
class UnaryNode(Node):
def __init__(self, child):
self.child = child
self.hash = hash(NOT) + hash(self.child)
def __eq__(self, other):
if not isinstance(other, UnaryNode):
return False
return self.child == other.child
def __hash__(self):
return self.hash
def __str__(self):
return str(NOT) + self.child.bracket_if_necessary()
def __invert__(self):
return self.child
class VariadicNode(Node):
def __init__(self, op, children):
self.op = op
self.children = children
self.hash = hash(self.op) + sum(hash(child) for child in self.children)
def __eq__(self, other):
if not isinstance(other, VariadicNode):
return False
return self.op is other.op and set(self.children) == set(other.children)
def __hash__(self):
return self.hash
def __str__(self):
return (" " + str(self.op) + " ").join(child.bracket_if_necessary() for child in self)
def __invert__(self):
return VariadicNode(AND if self.op is OR else OR, tuple(~c for c in self))
def bracket_if_necessary(self):
return "(" + str(self) + ")"
def __iter__(self):
return iter(self.children)
def __contains__(self, item):
return item in self.children
If I run this and try things like
Leaf("36") == Leaf("36)
~Leaf("36") == ~Leaf("36")
~~Leaf("36") == Leaf("36")
they all return True, as expected.
However, I'm running into bugs in the code that utilizes these nodes:
# Simplify procedure in DPLL Algorithm
def _simplify(cnf, l):
# print for debugging
for c in cnf:
print("b", l, ~l, type(l), "B", c, type(c), l in c)
return VariadicNode(AND, tuple(_filter(c, ~l) for c in cnf if l not in c))
# Removes the chosen unit literal (negated above) from clause c
def _filter(c, l):
# print for debugging
for x in c:
print("a", l, type(l), "A", x, type(x), x==l)
return VariadicNode(c.op, tuple(x for x in c if x != l))
Here cnf is given as a VariadicNode(AND) with all children being VariadicNode(OR). Children of VariadicNode are always given as a tuple.
These two prints result in lines like:
a ¬25 <class 'operators.UnaryNode'> A ¬25 <class 'operators.UnaryNode'> False
b ¬25 25 <class 'operators.UnaryNode'> B ¬25 ∨ ¬36 <class 'operators.VariadicNode'> False
which should not happen (¬25 == ¬25 in the first line and ¬25 in (¬25 ∨ ¬36) in the second should both return True). However there is also a line in the output:
b ¬25 25 <class 'operators.UnaryNode'> B ¬25 <class 'operators.VariadicNode'> True
so the check ¬25 in (¬25) actually does return True as it should.
Can anyone tell me what's going on?
If more info is needed, the rest of the code is available at [deleted] (hopefully it's publicly available, I'm pretty new to github so I don't know their policies). Note that it is still a WIP though.
The classes are located in operators.py and the rest of the (relevant) code is in SAT_solver.py, while test.py allows for easy running of the entire project, provided the networkx library is installed.
EDIT
I've now pushed a sample dimacs .txt file to the github repository that results in the described problem. Simply download hamilton.txt, SAT_solver.py and operators.py from the repository to the same folder, run SAT_solver.py (which has a main() method) and input hamilton or hamilton.txt to the command line when prompted for the problem file name (just leave the solution file name empty when prompted to prevent the program from writing any files). This should result in a lot of output, including problematic lines as described above.
The code you posted is not the code in your github repo. Your code has UnaryNode.__eq__ of
def __eq__(self, other):
if not isinstance(other, UnaryNode):
return False
return self.child == other.child
the repo code has
def __eq__(self, other):
if not isinstance(other, UnaryNode):
return False
return self.op is other.op and self.child == other.child
which also requires that the operators are identical. Instrumenting your code and breaking at a failure shows that you're generating two different NOT operators somewhere:
>>> str(x)
'¬24'
>>> str(l)
'¬24'
>>> x == l
False
>>> x.child == l.child
True
>>> str(x.op)
'¬'
>>> str(l.op)
'¬'
>>> x.op == l.op
False
>>> id(x.op)
2975964300
>>> id(l.op)
2976527276
Track down where that's happening and fix it however you like, whether by avoiding having more than one or by not caring whether there are more than one (my preference). I know in a deleted comment you wrote "The operators are single objects and I want them to be compared for equality based on their references", but (1) they're not single objects, and (2) if you didn't want such an unnecessary thing you wouldn't have woudn up in trouble..
If I had to guess, the other operators are being introduced when you call copy.deepcopy, but you're definitely not working with singletons.