Override __eq__ with more logic - python

I have the following python classes
class Message:
def __init__(self, start_date, attributes):
self.start_date = start_date
self.attributes = attributes
def __eq__(self, other):
if not isinstance(other, Message):
return False
if not self.attributes == other.attrubutes:
return False
are_consecutive_dates = False
self_start_date= datetime.strptime(self.start_date, '%Y-%m-%d')
other_start_date= datetime.strptime(other.start_date, '%Y-%m-%d')
if not abs(self_start_date.toordinal() - other_start_date.toordinal()) == 1:
return False
return True
class Attribute:
def __init__(self, attribute_a, attribute_b):
self.attribute_a = attribute_a
self.attribute_b = attribute_b
def __eq__(self, other):
if not isinstance(other, Attribute):
return False
if not self.attribute_a == other.attribute_a:
return False
if not self.attribute_b == other.attribute_b:
return False
return True
From a business perspective, two messages are equals if have the same attributes and have consecutive dates
I have two questions:
Is valid to have some business logic inside the __eq__(like the dates are consecutive)?
If the above is valid, I would like to create a set and pass the
Messages instances and discard the ones that are equals by the definition I just wrote, so
how I need to override the __hash__?

Messages are equal if they contain consecutive dates? That doesn't seem right. Equality normally has three properties:
Reflexive: a == a. This relation isn't reflexive as messages aren't equal to themselves.
Symmetric: if a == b then b == a. Your relation is symmetric since you use abs.
Transitive: if a == b and b == c then a == c. It's not transitive. Jan 1 and Jan 3 are not consecutive even though both are consecutive with Jan 2.
By violating these properties you can't use your objects in sets or as dictionary keys. You can't usefully implement __hash__ to match this definition of __eq__: a message isn't equal to itself, but its hash will be equal to itself. This will confuse any data structure that uses either method.
Don't use __eq__ for this. It's not the right relation. Make a new method are_consecutive.

Related

How to compare whether two subclasses have the same superclasses?

Currently I am learning Python and the concept of inheritance. When I tried to create one class called Animal and its subclass Rabbit. I also want to create the specific method to create a baby Rabbit from two mating ones and compare if two baby Rabbits have same parents.
However when I tried to compare them using the sample below,
r1 = Rabbit(3)
r2 = Rabbit(4)
r3 = r1+r2
r4 = r1+r2
print(r3 == r4)
I first used as follows (rid is just the sequential tag for each instance created)
parents_same = self.parent1.rid == other.parent1.rid \
and self.parent2.rid == other.parent2.rid
parents_opposite = self.parent2.rid == other.parent1.rid \
and self.parent1.rid == other.parent2.rid
return parents_same or parents_opposite
It went right, but when I tried to use:
parents_same = self.parent1 == other.parent1 \
and self.parent2 == other.parent2
parents_opposite = self.parent2 == other.parent1 \
and self.parent1 == other.parent2
return parents_same or parents_opposite
It showed that NoneType object has no attribute parent1. Moreover, when I revise the code that make the debugging easier, it went to say self is type "string" and not have the attribute parent1. I am totally at loss with such a situation.
key code that I used and may be useful for reference:
class Rabbit(Animal):
tag = 1
def __init__(self, age, parent1=None, parent2=None):
Animal.__init__(self, age)
self.parent1 = parent1
self.parent2 = parent2
self.rid = Rabbit.tag
Rabbit.tag += 1
def get_rid(self):
return str(self.rid).zfill(3)
def get_parent1(self):
return self.parent1
def get_parent2(self):
return self.parent2
def __add__(self, other):
# returning object of same type as this class
return Rabbit(0, self, other)
def __eq__(self, other):
# compare the ids of self and other's parents
parents_same = self.parent1.rid == other.parent1.rid \
and self.parent2.rid == other.parent2.rid
parents_opposite = self.parent2.rid == other.parent1.rid \
and self.parent1.rid == other.parent2.rid
return parents_same or parents_opposite
def __str__(self):
return "rabbit:"+ self.get_rid()
Checking for sibling hood is not what people expect == to do. Not even you, in this case: your equality method calls itself on the parents rather than some other notion of equality. This eventually ends up being called on objects who have no parents (or rather parents of None).
Nonetheless, this should not throw an error in this specific example, as the rabbits you're comparing (r3 and r4) have an equal number of generations. But they will always compare equal, as will (r1 and r2), because None compares equal to None.
If you must have this weird equality operator that instead tests for sibling hood, you should at least check the parents are the same using the python is, which will not have this recursive behavior.
If you compare r1 to r3 you will definitely get an error, however.

Instance Method returning the wrong value

I have a class for a Dialogue system as follows
class DIALOGUE(object):
def __init__(self, place, who, sTime, eTime, isActive, mood, menuText, func, repeatable, num):
self.place = place
self.who = who
self.sTime = sTime
self.eTime = eTime
self.isActive = isActive
self.mood = mood
self.menuText = menuText
self.func = func
self.repeatable = repeatable
self.num = num
#property
def ACheck(self):
global Date
if self.sTime == "none":
return True
else:
tHour,tMin = self.sTime.split(":")
if tHour >= Date.Hour and tMin <= Date.Minute:
tHour,tMin = self.eTime.split(":")
if tHour < Date.Hour and tMin < Date.Minute:
return True
return False
#property
def BCheck(self):
global Act
if self.who == Act:
return True
else:
return False
#property
def CCheck(self):
global Location
if self.place == Location:
return True
if self.place == "none":
return True
return False
#property
def DCheck(self):
if self.repeatable:
return True
else:
if self.num > 0:
return False
else:
return True
#property
def CanChat(self):
if self.isActive and self.ACheck and self.BCheck and self.CCheck and self.DCheck:
return True
else:
return False
def SetActive(self):
self.isActive = True
def Do(self):
self.num += 1
renpy.call(self.func)
Most of this should be self explanatory but I parse an XML file into a list of Instances of this class.
The user is presented with a list of available dialogues based on what Location they are in, what time of day it is and what NPC they have selected. If the dialogue is not repeatable The DCheck method looks at whether or not the dialogue has been completed before i.e if the dialogue is not repeatable and self.num > 0 the method will return False
Essentially it loops through all the dialogues and carries out i.CanChat and if this value returns True, the Dialogue is added to the menu
The issue I'm having is that the Check methods aren't returning the correct value. Specifically DCheck is returning True all the time, regardless of whether the Dialogue is repeatable or not, and ignoring the value of self.num
The class is created in an init python: block and then the xml file is parsed in a separate python block which is called from inside the start label
It's probably something really simple but I can't figure it out.
The list of instances is parsed as follows
Dialogues = []
for j in Dialo:
JPlace = j.find('Place').text
JWho = j.find('Who').text
JsTime = j.find('Start').text
JeTime = j.find('End').text
JMood = int(j.find('Mood').text)
JText = j.find('Text').text
JFunc = j.find('Func').text
JRep = j.find('Rep').text
if JRep == "True":
Jrep = True
else:
Jrep = False
Dialogues.append(DIALOGUE(JPlace, JWho, JsTime, JeTime, False, JMood, JText, JFunc, JRep, 0))
The method for creating the menu is as follows
def TalkCheck():
talks = []
talks.append(("Nevermind.", "none"))
for i, q in enumerate(Dialogues):
if q.CanChat:
talks.append((q.menuText,i))
renpy.say(None, "", interact=False)
talkchoice = renpy.display_menu(talks)
if talkchoice <> "none":
talkchoice = int(talkchoice)
Dialogues[talkchoice].Do()
Your question is incomplete - you didn't post a MCVE, we don't know the effective values for "repeatble" and "num" that leads to this behaviour, and we don't even know if it's using Python 2.x or Python 3.x - so we can just try and guess. Now since you mention that you "parse an XML file into a list of instances", I stronly suspect you are running Python 2.x and passing those values as strings instead of (resp.) boolean and int. In Python 2, "-1" (string) compares greater than 0 (int) - it raises a TypeError in Python 3.x -, and in both cases a non-empty string evals to True in a boolean context (bool('False') == True). Since there's no obvious logical error in your method implementation, that's the only explanation I can think of.
BTW, expressions have a boolean values and return exits the function, so you can simplify your code:
#property
def DCheck(self):
if self.repeatable:
return True
return self.num > 0

Method to compare Python dictionaries fails with certain value types?

I can't figure this out. I have two dictionaries which are identical. I use a standard method to determine the differences, of which there should be none. But certain value types are always returned as differences, even when they are not. For example, if a value is a pymongo.bson.ObjectId, the method fails to evaluate it as the same.
d1 = {'Name':'foo','ref1':ObjectId('502e232ca7919d27990001e4')}
d2 = {'Name':'foo','ref1':ObjectId('502e232ca7919d27990001e4')}
d1 == d2
returns:
True
But:
set((k,d1[k]) for k in set(d1) & set(d2) if d1[k] != d2[k])
returns:
set([('ref1',Objectid('502e232ca7919d27990001e4'))])
So I've figured out that this is weird, no?
d1['ref1'] == d2['ref1'] # True
d1['ref1'] != d2['ref1'] # False
What the?????!?!??!!?
ObjectId('502e232ca7919d27990001e4') creates a new object and by default != compares references. Try for example:
class Obj:
def __init__(self, value):
self.value = value
print Obj(1234) == Obj(1234) # False
This will evaluate to false, because they are difference instances, even if they hold the same value. To make this work, the class must implement the eq method:
class Obj:
def __init__(self, value):
self.value = value
def __eq__(self, other):
return self.value == other.value
print Obj(1234) == Obj(1234) # True
To fix this, you can "monkey-patch" the class:
class Obj:
def __init__(self, value):
self.value = value
print Obj(1234) == Obj(1234) # False
Obj.__eq__ = lambda a, b: a.value == b.value
print Obj(1234) == Obj(1234) # True
Or compare them by their values directly.
print Obj(1234).value == Obj(1234).value
Compare the values when possible because monkey-patching may break seemingly unrelated code.

Object membership in sets

This is really two questions:
Why isn't the membership operator (__contains__) ever being called?
Why is D in nodeList, but not in nodeSet?
My goal is for D to be "in" both nodeList and nodeSet, because it has the same loc as A.
class Node(object):
def __init__(self, loc):
self.loc = loc
def __eq__(self, other):
print "eq: self.getLoc(): {}, other.getLoc(): {}".format(self.getLoc(), other.getLoc())
if self.getLoc() == other.getLoc():
return True
return False
def __contains__(self, other):
print "contains: self.getLoc(): {}, other.getLoc(): {}".format(self.getLoc(), other.getLoc())
if self.getLoc() == other.getLoc():
return True
return False
def setLoc(self, loc):
self.loc = loc
def getLoc(self):
return self.loc
if __name__ == "__main__":
A = Node((1,1))
B = Node((2,2))
C = Node((3,3))
D = Node((1,1))
nodeList = [A, B, C]
nodeSet = set()
nodeSet.add(A)
nodeSet.add(B)
nodeSet.add(C)
print "A in nodeList: {}".format(A in nodeList)
print "A in nodeSet: {}".format(A in nodeSet)
print "D in nodeList: {}".format(D in nodeList)
print "D in nodeSet: {}".format(D in nodeSet)
This returns True, True, True, False. Apparently, the __contains__ operator is never called. I would like it to return True, True, True, True.
Any other critiques of my code are of course welcome, as I am a python beginner.
Why would Node.__contains__ ever be called? You never have a Node as the right-hand-side of an in expression.
See the documentation re __hash__() - in short:
[I]f [a class] defines __cmp__() or __eq__() but not __hash__(),
its instances will not be usable in hashed collections.
A set is a hashed collection. You'll want to make sure you implement Node.__hash__()

Python Range Class/Subclass

I have code for a Range class like this:
class Range:
def __init__(self, start, end):
self.setStart(start)
self.setEnd(end)
def getStart(self):
return self.start
def setStart(self, s):
self.start = s
def getEnd(self):
return self.end
def setEnd(self, e):
self.end = e
def getLength(self):
return len(range(self.start, self.end))
def overlaps(self, r):
if (r.getStart() < self.getEnd() and r.getEnd() >= self.getEnd()) or \
(self.getStart() < r.getEnd() and self.getEnd() >= r.getEnd()) or \
(self.getStart() >= r.getStart() and self.getEnd() <= r.getEnd()) or \
(r.getStart() >= self.getStart() and r.getEnd() <= self.getEnd()):
return True
else:
return False
My assignment is to create a subclass of Range, called DNAFeature, that represents a Range that also has a strand and a sequence name:
Implement setStrand and getStrand, which set and return strand information, and setSeqName and getSeqName, which set or return the name of the sequence the feature belongs to.
If a feature is on the minus (reverse) strand, getStrand() should return ‐1. If a feature is on the plus strand, getStrand() should return 1. If strand is not set, getStrand() should return 0.
I have tried to write something but doesn't look right at all for me, can everyone please help me with this, thank you so much guys, this is my code:
class DNAFeature(Range):
def __init__(self, strand, sequence):
self.setStrand(strand)
self.setSeqName(sequence)
def getSeqName(self):
return self.plus or minus
def setSeqName(self, seq):
self.sequence = seq
def getStrand(self):
if self.getSeqName(self.strand) == 'plus':
return 1
if self.getSeqName(self.strand) == 'minus':
return -1
else:
return 0
def setStrand(self, strand):
self.strand = strand
In general it is much easier to answer questions if you provide a specific error message or thing that is going wrong. Here's what happened when I tried to run the above:
First up:
`SyntaxError: invalid syntax`
on if seq == POSITIVE. What's wrong here? Oh yes, you're missing a colon after the conditional. If you add that the file at least parses. So let's try doing some coding:
# Your code here, then:
feature = DNAFeature()
Running that gives:
TypeError: __init__() takes exactly 3 positional arguments (1 given)
Oh, OK, we need to pass some arguments to the initialiser of DNAFeature. Let's put this on the + strand, and call it foo:
feature = DNAFeature(1, "foo")
Now we get:
AttributeError: 'DNAFeature' object has no attribute 'setStrand'
What's that about? OK, you haven't defined setStrand. (Note: you shouldn't have to. But more on that later.) Let's define it:
def setStrand(self, strand):
self.strand = strand
I don't want to go through the rest of the problems with the code (hint: you need to define variables before you use them), but this is the sort of thing you should be doing.
Right, something different. The above is bad code. I hope you've written the Range class and that it hasn't been provided as part of the course, because if it has you're taking a badly-taught course. The main problem is the use of getters and setters -- I'm guessing you're Java-born and bred? In Python you don't need to write getters and setters for everything, because you can always add them in later if you need them. Instead, just use class attributes. Look at the following code for Range:
class Range:
def __init__(self, start, end):
self.start = start
self.end = end
def length(self):
return self.end - self.start
def overlaps(self, other):
return not(self.end < other.start or other.end < self.start)
Isn't that much nicer? No more nasty accessors, no icky comparisons in the overlaps method... It helps if you work out the logic that your code is trying to implement before you implement it.
See if you can write a better DNAFeature now.
You still haven't told me what getStrand should, do, but here's what I think you're aiming towards. Suppose the strand name that gets passed to __init__ is of the form "+name" or "-name". You can then do the following:
def __init__(self, strand):
sequence = strand[0] #first character of strand
if sequence == "+":
self.strand = 1
self.sequence= strand[1:]
elif sequence == "-":
self.strand = -1
self.sequence = strand[1:]
else:
self.strand = 0
self.sequence = strand
See if you can work out how that works.
In the most generic case (without making any assumptions), it seems that this is what you need:
class DNAFeature(Range):
def __init__(self, start, end):
self.setStart(start)
self.setEnd(end)
self.strand = None
self.sequencename = None
def setStrand(self, s):
self.strand = s
def getStrand(self):
if self.sequenceName == 'plus':
return 1
elif self.sequenceName == 'minus':
return -1
else:
return 0
def setSequenceName(self, s):
self.sequencename = s
def getSequenceName(self, s):
return self.sequenceName
You will notice that here, I have redefined init. There is a reason for this. I remember that in one of your earlier questions, you had mentioned that this was a Java assignment, just renamed to python. In Java, constructors are not inherited (correct me if I'm wrong). Therefore, if the same grading rubric is being used, you will lose marks for not redefining the constructor here.
Hope this helps

Categories

Resources