How to compare whether two subclasses have the same superclasses? - python

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.

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.

How can I conduct a search, using a partial string, to search a list of objects with string parameters and find the object I'm looking for in Python?

What I'm creating is a type of store. I can add product objects to said store(also an object) with multiple parameters, including a title(string), description(string), and product_number(integer).
I would like to create a function that takes as a parameter a string that could be used to search the title and description parameters of all product objects in the inventory(list), find matches(partial and full, it doesn't matter in this case), append those matching objects to a list called search_results, sort them in ascending order by their product_number, and return them to the user.
class Product:
def __init__(self, code, title, desc, price, quantity):
self._code = code
self._title = title
self._desc = desc
self._price = price
self._quantity = quantity
def decrease_quantity(self):
self._quantity = self._quantity - 1
def get_product_code(self):
return self._code
def get_product_title(self):
return self._title
def get_product_price(self):
return self._price
def get_product_quantity(self):
return self._quantity
def get_product_desc(self):
return self._desc
class Store:
def __init__(self):
self._store_inventory = []
self._store_members = []
def product_search(self, product_keyword):
search_results = []
for product in self._store_inventory:
if product_keyword in product.get_product_title():
search_results.append(product)
elif product_keyword in product.get_product_desc():
search_results.append(product)
else:
return "Not Found"
search_results.sort()
return search_results
This is what I have so far.
The main problem I have(there are a few, i know) is that I cannot get the product_search function to work properly. That is, it always reverts the the "else" case and returns "Not Found", even when I give the exact case and spelling of the product title.
What is it I'm doing wrong in that regard?
You may need to implement the function __eq__ for the product class, according to the docs
For container types such as list, tuple, set, frozenset, dict, or collections.deque, the expression x in y is equivalent to any(x is e or x == e for e in y).
The reason your in operator does not work because the __eq__ function for the class not implemented so Python does not know how to compare your object instances (what properties to compare,...)
Read more about __eq__
Example:
class Foo:
def __init__(self, a):
self.a = a
class Bar:
def __init__(self, a):
self.a = a
def __eq__(self, other):
return self.a == other.a
f1 = Foo(5)
f2 = Foo(5)
print("f1 == f2", f1 == f2)
fl = [f1,f2]
f3 = Foo(5)
print("f3 in fl", f3 in fl)
g1 = Bar(5)
g2 = Bar(5)
print("g1 == g2",g1 == g2)
gl = [g1,g2]
g3 = Bar(5)
print("g3 in gl", g3 in gl)
Output:
f1 == f2 False
f3 in fl False
g1 == g2 True
g3 in gl True

Why is equality attribute of a class calling itself over and over?

I'm using MIT OCW and just learned about classes. So when equality method is called on a pair of instances, my code (edited from the original) is calling itself over and over again. The code is as follows:
class Animal(object):
def __init__(self, age):
self.age = age
self.name = None
def __str__(self):
return "animal:"+str(self.name)+":"+str(self.age)
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 __eq__(self, other):
print('entering equality')
print(self.parent1)
print(self.parent2)
parents_same = self.parent1== other.parent1 and self.parent2== other.parent2
print('1st comp')
parents_opposite = self.parent2 == other.parent1 and self.parent1== other.parent2
print('2nd comp')
return parents_same or parents_opposite
a=Rabbit(6)
b=Rabbit(7)
c=Rabbit(5,a,b)
d=Rabbit(3,a,b)
e=Rabbit(2,c,d)
f=Rabbit(1,c,d)
print(e==f)
When this code is run, it is seen that Python enters equality loop multiple times.
Below is the original eq attribute:
def __eq__(self, other):
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
The code runs just fine with the original equality attribute.
Can anyone explain me why this is happening. Thank You.
Because you are checking multiple rabbits for equality! The parents of e, f objects are rabbits too, and each of them has rabbits for parents. So, each equality check will call Rabbit.__eq__ recursively, until you get to a and b

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