multistep comparison test python - python

I wanna implement a class overloading and conclude if an event with a given point of time for example 12:59:50 happens before another event so the output is true or false, just a simple comparison test. I implemented it, as you can see, but, i'm pretty much sure this is not the most pythonic or better to say, objected oriented approach to carry out the tasks. I'm new to python so is there any improvement out there ?
Thanks
def __lt__(self, other):
if self.hour < other.hour:
return True
elif (self.hour == other.hour) and (self.minute < other.minute):
return True
elif (self.hour == other.hour) and (self.minute == other.minute) and (self.second < other.second):
return True
else:
return False

Tuples (and other sequences) already perform the type of lexicographic comparison you are implementing:
def __lt__(self, other):
return (self.hour, self.minute, self.second) < (other.hour, other.minute, other.second)
The operator module can clean that up a little:
from operator import attrgetter
def __lt__(self, other):
hms = attrgetter("hour", "minute", "second")
return hms(self) < hms(other)

Related

How to use multiple comparison operators at once

class Fraction:
def __init__(self, top, bottom):
self.top = top
self.bottom = bottom
def __repr__(self):
return f"{self.top}/{self.bottom}"
def __ne__(self, other):
ne_first_top = self.top * other.bottom
ne_second_top = self.bottom * other.top
return ne_first_top != ne_second_top
def __eq__(self, other):
first_top = self.top * other.bottom
second_top = other.top * self.bottom
return first_top == second_top
f1 = Fraction(2, 3)
f3 = Fraction(1, 4)
assert f1 != f3 == True
When I run this code, I get the error AttributeError: 'bool' object has no attribute 'bottom'. Can I run this code without changing last line?
The last line is incorrect because f3 == True is being evaluated first. It'd work if you did:
assert (f1 != f3) == True
or more simply (because True == True is always True):
assert f1 != f3
Technically you could make it "work" by forcing f3 == True to return f3, so that f1 != (f3 == True) will actually do what you want it to do:
def __eq__(self, other):
if other == True:
return self
but don't do that. It would be extremely silly.
Note that since you've defined __eq__, you don't need to explicitly define __ne__ as its opposite. If __ne__ isn't explicitly defined it will just automatically be interpreted as "not __eq__"; Python doesn't generally force you to do extra work if it can at all be avoided.
If you want to compare objects of different classes, you could amend your eq() method to handle it gracefully. At its simplest, you can do:
def __eq__(self, other):
if type(self) == type(other):
first_top = self.top * other.bottom
second_top = other.top * self.bottom
return first_top == second_top
else:
return False
Of course, if you want Fraction(1,1) == True to return True, then you'll need to elaborate your __eq__() method a bit further.

Override __eq__ with more logic

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.

Combination Lock Program

Me and my partner have been working on this for a few hours and can't figure this out. The directions are vague in some areas and our professor did not do a good job of breaking it down to help us. Here is a link to the directions. I believe they are not very clear but please correct me if I am wrong and just overthinking it https://imgur.com/a/huHnwos
I believe that our biggest problems are the unlock(combination) and set_new_combination(new_combination) methods. I can figure out the str() method as that one isn't very hard to do. We've tried the things our professor has told us to try but they have been unsuccessful.
class Lock:
def __init__(self, combination = 0):
self.combination = combination
self.locked = False
def lock(self):
self.locked = True
def unlock(self, combination):
if combination == True or combination == 0:
self.locked = False
def set_new_combination(self, new_combination):
if self.locked == False:
self.combination = new_combination
def is_locked(self):
if self.locked == True or self.combination == True:
return True
else:
return False
def __eq__(self, other):
if other is not None and type(other) == type(self):
if self.combination == other.new_combination:
return False
def __str__(self):
return self.combination, ',', self.locked
The expected result should be a working basic combination lock.
Here's my implementation based on the inctructions provided, with comments where it deviates from your code.
class Lock:
def __init__(self, combination = 0): # No change here
self.combination = combination
self.locked = False
def lock(self):
# Although a test of self.locked is redundant, the instructions state
# "...if invoked a second time this, method should do nothing."
if not self.locked:
self.locked = True
def unlock(self, combination):
# You were not testing the stored combination against the one passed to the method.
# it does not matter if the stored combination is zero or a different number,
# you still need to check for equality.
# You also need a test as with lock() to satisfy the "if invoked a second time this,
# method should do nothing" requirement.
if self.locked and self.combination == combination:
self.locked = False
def set_new_combination(self, new_combination):
# You can simply the `if` condition, there's no need to test against False
if not self.locked:
self.combination = new_combination
def is_locked(self):
# I don't know why you are testing the self.combination value, you
# only need to return the state of the lock
return self.locked
def __eq__(self, other):
# You have the correct guard conditions but were returning False when
# the combinations matched. You can simply return the comparison result.
if other is not None and type(other) == type(self):
return self.combination == other.new_combination
def __str__(self):
# For some reason the output format specified for this appears to put it in a list
# (the square brackets) but as it's only for display we'll "fake" the list.
# The `if` statement prints the word 'locked' or 'unlocked' depending on the
# `self.locked` state.
return '[{}, {}]'.format(self.combination, 'locked' if self.locked else 'unlocked')
There are couple of problems with your code. First, if statement in your unlock method will be executed only if combination == 0 or combination == 1, which has nothing to do with lock's combination (self.combination). In your is_locked method you should only return self.locked, no need for if. __eq__ method can also be simplified. And __str__ method should actually return String.
class Lock:
def __init__(self, combination = 0):
self.combination = combination
self.locked = False
def lock(self):
self.locked = True
def unlock(self, combination):
if self.combination == combination:
self.locked = False
def set_new_combination(self, new_combination):
if not self.locked:
self.combination = new_combination
def is_locked(self):
return self.locked
def __eq__(self, other):
return isinstance(other, Lock) and self.combination == other.combination
def __str__(self):
return f'{self.combination}, { "locked" if self.locked else "unlocked"}'
Your unlockmethod is trying to compare a boolean to a number (the combination). Change it to look like this:
def unlock(self, combination):
if combination == self.combination:
self.locked = False
You also did this in your is_locked method, so that should be changed too:
def is_locked(self):
return self.locked
(Any time you find yourself writing something along the lines of if x return True else return False you can almost always replace this with return x if the conditional is simple).
set_new_combination works fine; I don't know what issue you saw with it.
Finally, your __str__ method should actually return a string:
def __str__(self):
return '[' + str(self.combination) + ', ' + 'locked' if self.locked else 'unlocked' + ']'

python >= operator on classes

I have a question regarding python '>=' behaviour.
I have an old TimeStamp class, which holds (hour, minute) tuple and offers some methods such as __eq__, __gt__, __lt__.
I am refactoring it to also account for day and second, and to store the data as total seconds. Here I implemented __eq__, __gt__, __lt__ as well.
However, further in code I am using >= operator for this class and while the old class version is working properly, with the new one I am getting
TypeError: unorderable types: TimeStamp() >= TimeStamp() error.
Code is below:
class TimeStamp(tuple): # OLD, WORKING VERSION
"""TimeStamp, hh:mm tuple supporting comparison and addition"""
__slots__ = ()
def __new__(cls, *args):
if len(args) == 1: # tuple entrance
hour, minute = args[0]
elif len(args) == 2: # hour, minute entrance
hour, minute = args[0], args[1]
else:
raise TypeError('wrong input to TimeStamp')
div, minute = divmod(minute, 60)
hour += div
_, hour = divmod(hour, 24)
return tuple.__new__(cls, (hour, minute))
#property
def abs_min(self):
return self.hour * 60 + self.minute
def __gt__(self, rhs):
return self.abs_min > rhs.abs_min
def __lt__(self, rhs):
return self.abs_min < rhs.abs_min
def __eq__(self, rhs):
return self.abs_min == rhs.abs_min
New version:
class TimeStamp:
def __init__(self, *args):
for argument in args:
if not isinstance(argument, int):
raise TypeError("Can only build TimeStamp from ints, not: " + str(argument))
if len(args) == 1: # init by abs
self.abs = args[0] # put the ELEMENT, not the tuple itself
elif len(args) == 2: # init by hour:minute
hour, minute = args
self.abs = hour * 60 * 60 + minute * 60
elif len(args) == 4: #init by day:hour:minute:second
day, hour, minute, second = args
self.abs = day * 24 * 60 * 60 + hour * 60 * 60 + minute * 60 + second
else:
raise TypeError("wrong data for TimeStamp: " + str(args))
def __eq__(self, other):
if isinstance(other, TimeStamp):
return self.abs == other.abs
else:
raise TypeError("wrong argument for comparison: " + str(other))
def __gt__(self, other):
if isinstance(other, TimeStamp):
return self.abs > other.abs
else:
raise TypeError("wrong argument for comparison: " + str(other))
def __lt__(self, other):
if isinstance(other, TimeStamp):
return self.abs < other.abs
else:
raise TypeError("wrong argument for comparison: " + str(other))
Now for the comparison part:
if args[1] >= self.start:
>>TypeError: unorderable types: TimeStamp() >= TimeStamp()
I have found two fixes: first, to replace my comparison line with
if args[1] > self.start or args[1] == self.start:
or an alternative, to add
def __ge__(self, other):
if isinstance(other, TimeStamp):
return self.abs >= other.abs
else:
raise TypeError("wrong argument for comparison: " + str(other))
to my new class. However, the old one did work with neither of those fixes. It looks to me as if Python stopped deducting that ((a>b) or (a==b)) implies (a>=b). But why did it work before? Does it have something to do with me subclassing tuple?
PS. don't be scared by my __init__ code, which I included for completeness. It's supposed to be overload-like, but I might be doing it in a non-pythonic way (still learning)
The old one worked because it inherited from tuple, and tuple provides __ge__. Your new version does not inherit from tuple, so it doesn't have a __ge__ method.
Even in your old version, your __gt__ and __lt__ methods were never being called when using >= (as you can verify by putting print inside those methods). The underlying tuple.__ge__ was being called instead. However, for your case, the effect is the same, so you didn't notice. That is, given that the "minutes" number is always less than 60, comparing (hours, minutes) tuples in the usual way is equivalent to comparing 60*hours + minutes. So I don't think you really need to define the comparison methods at all if you inherit from tuple.

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