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
Related
I am creating an object that represents the hand of a blackjack player. One of the methods of the hand is to add a new Card to it. However, my Hand object always returns None when I attempt to print it.
Here is my code of the Hand object.
class Hand:
'''An object representing the Card objects that the player has in their hands'''
def __init__(self, name):
self.name = name
self.list = []
def addCard(self, card):
self.list = self.list.append(card)
return self.list
def __str__(self):
return f'Your hand has {self.list}.'
myHand = Hand('Henry')
myHand.addCard(str(myCard))
print (myHand)
myCard is an object that returns "Four of Diamonds" I created previously. Below is the whole code if you are interested.
class Card:
''' A class for representing a single playing card. '''
def __init__(self, value, suit):
''' Creates Card object with given suit and value. '''
self.value = value
self.suit = suit
def getSuit(self):
''' Returns the suit of the Card. '''
return self.suit
def getValue(self):
''' Returns the value of the Card. '''
return self.value
def getBlackjackValues(self):
''' Get a list of possible Blackjack values for the card. '''
# IMPLEMENT ME
if 1 < self.value:
BlackjackValue = self.value
return [BlackjackValue]
else:
BlackjackValue = [self.value, 11]
return BlackjackValue
def __str__(self):
''' #Return a string representation of the Card. '''
# IMPLEMENT ME
# Convert numerical values into letters
if self.value == 2:
Value = 'Two'
elif self.value == 3:
Value = 'Three'
elif self.value == 4:
Value = 'Four'
elif self.value == 5:
Value = 'Five'
elif self.value == 6:
Value = 'Six'
elif self.value == 7:
Value = 'Seven'
elif self.value == 8:
Value = 'Eight'
elif self.value == 9:
Value = 'Nine'
elif self.value == 10:
Value = 'Ten'
elif self.value == 11:
Value = 'Jack'
elif self.value == 12:
Value = 'Queen'
elif self.value == 13:
Value = 'King'
elif self.value == 1:
Value = 'Ace'
# Convert suit values into letter
if self.suit == 'S':
Suit = 'Spades'
elif self.suit == 'H':
Suit = 'Hearts'
elif self.suit == 'D':
Suit = 'Diamonds'
elif self.suit == 'C':
Suit = 'Clubs'
# The card is
return f'Your card is {Value} of {Suit}.'
myCard = Card (4, 'D')
print (myCard)
class Hand:
'''An object representing the Card objects that the player has in their hands'''
def __init__(self, name):
self.name = name
self.list = []
def getName(self):
return self.name
def getList(self):
return self.list
def addCard(self, card):
self.list = self.list.append(card)
return self.list
def __str__(self):
return f'Your hand has {self.list}.'
myHand = Hand('Henry')
myHand.addCard(str(myCard))
print (myHand)
Here is the screenshot of the output:
Output
list.append() method works in place, i.e. it returns None. That is what you assign to self.list. Note that if you try to add second card it will raise an error, because None has no append attribute.
All I need to do is use .append() at the return statement in the addCard method.
Also, credits to #Nja for pointing out that I do not need to update myHand object again, but simply initiate the method.
Try this:
class Hand:
'''An object representing the Card objects that the player has in their hands'''
def __init__(self, name):
self.name = name
self.list = []
def addCard(self, card):
self.list.append(card)
def __str__(self):
return 'Your hand has ' + ' '.join(self.list)
myCard = "ciao"
myHand = Hand('Henry')
myHand.addCard(str(myCard))
print (myHand)
I have two classes (Student and Course). I'm trying to write a method for the Course class that will remove a given student from a course. However, there's a problem when I run
self.students.remove(student) in the method. The error tells me that student is not in the students list. Printing the students list I don't actually see the values, but instead I see a reference to it:
print(self.students)
> [<data.Student object at 0x7fc9980334f0>, <data.Student object at 0x7fc998033580>, <data.Student object at 0x7fc9980428b0>, <data.Student object at 0x7fc998042a00>]
However, if I select a specific student at an index then I'm able to see the actual data.
print(self.students[0])
> 2020411:King,Maha
Why is this happening when trying to print the students attribute?
Code if needed:
from copy import deepcopy
class Student:
def __init__(self, sid, last, first):
self.sid = sid
self.last = last
self.first = first
def __str__(self):
return '{}:{},{}'.format(self.sid, self.last, self.first)
def __repr__(self):
return '{}:{},{}'.format(self.sid, self.last, self.first)
class Course:
def __init__(self, crn, students):
self.crn = crn
self.students = deepcopy(students)
def key(self):
return self.crn
def is_empty(self):
return len(self.students) == 0
def get_student(self, student_key):
for student in self.students:
if student.key() == student_key:
return deepcopy(student)
return None
def __contains__(self, student):
for i in self.students:
if student.key() == i.key():
return True
break
return False
def register(self, student):
if student not in self:
self.students.append(deepcopy(student))
return
def drop(self, student):
s = None
if student in self:
s = deepcopy(student)
self.students.remove(student)
return s
student1 = Student(2020411, 'King', 'Maha')
student2 = Student(2019399, 'Hess', 'Alvin')
student3 = Student(2020301, 'Chin', 'Yu')
student4 = Student(2019111, 'Hay', 'Ria')
student_list = [student1, student2, student3]
course1 = Course('CP104', student_list)
removed_student = course1.drop(student2)
The issue with deepcopy() is that it creates an entirely new object that has the same attributes as the original one, yet they are not equal. For list.remove(), this compares the reference to check if the actual object exists. In your case, you are trying to remove an object that is not in the list.
Instead of removing it, if you want to return the student, use list.pop().
def drop(self, student):
for i, s in enumerate(self.students):
if s.sid == student.sid :
return self.students.pop(i)
As a side note, it will be easier to do operations if Course.students is a dictionary such that:
self.students = {
`sid1`: student1,
`sid2`: student2,
# etc
}
EDIT: Alternatively, implement __eq__() in Student so that list.remove() will work.
def __eq__(self, other):
return self.sid == other.sid and self.first == other.first and self.last == other.last
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.
I am using eval to run a generated string to append the newly created EggOrder instance to the list of the correct instance of the DailyOrders class. The day provided by EggOrder is used to used to append to the correct instance. This relies on eval and the variable name of the DailyOrders instance and so it would be great to get this removed. I know there must be a better way.
class DailyOrders:
PRICE_PER_DOZEN = 6.5
def __init__(self, day):
self.orders = []
self.day = day
def total_eggs(self):
total_eggs = 0
for order in self.orders:
total_eggs += order.eggs
return total_eggs
def show_report(self):
if self.total_eggs() < 0:
print("No Orders")
else:
print(f"Summary:\nTotal Eggs Ordered: {self.total_eggs()}")
print(f"Average Eggs Per Customer: {self.total_eggs() / len(self.orders):.0f}\n*********")
class EggOrder():
def __init__(self, eggs=0, name="", day=""):
if not name:
self.new_order()
else:
self.name = name
self.eggs = eggs
self.day = day
eval(f"{self.day.lower()}.orders.append(self)")
def new_order(self):
self.name = string_checker("Name: ")
self.eggs = num_checker("Number of Eggs: ")
self.day = string_checker("Date: ")
def get_dozens(self):
if self.eggs % 12 != 0:
dozens = int(math.ceil(self.eggs / 12))
else:
dozens = self.eggs / 12
return dozens
def show_order(self):
print(f"{self.name} ordered {self.eggs} eggs. The price is ${self.get_dozens() * DailyOrders.PRICE_PER_DOZEN}.")
if __name__ == "__main__":
friday = DailyOrders("Friday")
friday_order = EggOrder(12, "Someone", "Friday")
friday_order.show_order()
friday.show_report()
saturday = DailyOrders("Saturday")
saturday_order = EggOrder(19, "Something", "Saturday")
saturday_order = EggOrder(27, "Alex Stiles", "Saturday")
saturday.show_report()
DailyOrders isn't actually a superclass (it was in a earlier version), it acts like one and I suspect the answer might have some inheritance.
Basically, I have a class for the Player of a game
class Player:
def __init__(self,inventory,hp):
self.inventory = []
self.hp = 20
...
...
P = Player()
And I simply want to check for an item in the inventory (where items are each a class as well)
class Book():
def __init__(self,name,description):
...
...
Then do this.
if Book() in P.inventory:
print("You have a book.")
else:
print("You don't have a book.")
The problem I'm having is that even if the Book() object is in the player's inventory, it reads the if statement as false and runs the else statement.
I'm thinking I could try to use a for loop like so
for i in P.inventory:
counter = 0
if i == Book():
print("You have a book.")
counter = 1
if counter == 0:
print("You don't have a book.")
but I'm hoping I won't have to use that much code for such a simple task.
Book() creates a new object everytime so Book() == Book() returns False. You might want to use isinstance instead:
a_book = Book()
isinstance(a_book, Book)
with something like:
def check_for_books(inventory):
for i in inventory:
if isinstance(i, Book):
print("You have a book.")
return
else:
print("You have no books.")
Actually if just care about the book, treating the books with the same name and same description as an identity book. You'd better overwrite the eq and hash method of the book.
class Player:
def __init__(self, inventory=None, hp=None):
if inventory is None:
inventory = []
self.inventory = inventory
self.hp = hp
class Book:
def __init__(self, name, description):
self.name = name
self.description = description
def __eq__(self, other):
return other and self.name == other.name and self.description == other.description
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash((self.name, self.description))
player = Player([Book('Harry Potter', 'Volume I'), Book('Harry Potter', 'Volume II')], 20)
print(Book('Harry Potter', 'Volume I') in player.inventory) # True
print(Book('Harry Potter', 'Volume V') in player.inventory) # False
So you don't have to worry about if the book you wanna check is exactly the instance in memory. Due to in most cases, your data should be stored in db, not in the memory.
This works:
>>> class Book():
... def __init__(self,name,description):
... self.name = name
... self.description = description
...
>>> class Player:
... def __init__(self,inventory,hp):
... self.inventory = inventory
... self.hp = hp
...
>>> b = Book('myname', 'mydesc')
>>> p = Player([b], 'hp')
>>> b in p.inventory
True
This returns False because it is a different instance of Book:
>>> c = Book('myname', 'mydesc')
>>> c in p.inventory
False
You'll have to check the object type of the items in the inventory to see if they are of class Book()
for o in P.inventory:
if isinstance(o, Book()):
print("You have a book.")
break
else:
print("You don't have a book.")
Notice that the else statement is part of the for statement, not the if statement