Checking values in a class list - python

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

Related

How can I print each item in an unknown length list that is a class attribute as a string in python?

I have a class (Student) with different attributes, such as studentId, address, and courses. My str method for the class returns all the information that the user put in. However, for the attributes that are lists, such as courses, the location of the information is printed out instead of the actual information. Here is the code (sorry it's a little long, there's a bunch of classes):
class Person:
__name = None
__age = None
__address = None
def __init__(self, name, age=0, address=None):
self.set_name(name)
self.set_age(age)
self.set_address(address)
def __str__(self):
return 'Name: ' + self.__name + '\n' + \
'Age: ' + str(self.__age) + '\n' + \
'Address: ' + str(self.__address)
def set_name(self, name):
self.__name = name
def get_name(self):
return self.__name
def set_age(self, age):
self.__age = age
def get_age(self):
return self.__age
def set_address(self, address):
self.__address = address
def get_address(self):
return self.__address
class Student(Person):
def __init__(self, name, studentID= None, age= 0, address= None):
super(Student, self).__init__(name, age, address)
self.set_studentID(studentID)
self.__courses =[]
def __str__(self):
result = Person.__str__(self)
result += '\nStudent ID:' + self.get_studentID()
for item in self.__courses:
result += '\n ' + str(item)
return result
def set_studentID(self, studentID):
if isinstance(studentID, str) and len(studentID.strip()) > 0:
self.__studentID = studentID.strip()
else:
self.__studentID = 'NA'
def get_studentID(self):
return self.__studentID
def add_course(self, course):
print('in add_course')
self.__courses.append(course)
def get_courses(self):
for i in range(len(self.__courses)):
return self.__courses[i]
class Course:
__courseName = None
__dept = None
__credits = None
def __init__(self, courseName, dept= 'GE', credits= None):
self.set_courseName(courseName)
self.set_dept(dept)
self.set_credits(credits)
def __str__(self):
return self.get_courseName() + '/' + self.get_dept() + '/' + str(self.get_credits())
def set_courseName(self, courseName):
if isinstance(courseName, str) and len(courseName.strip()) > 0:
self.__courseName = courseName.strip()
else:
print('ERROR: Name must be a non-empty string')
raise TypeError('Name must be a non-empty string')
def get_courseName(self):
return self.__courseName
def set_dept(self, dept):
if isinstance(dept, str) and len(dept.strip()) > 0:
self.__dept = dept.strip()
else:
self.__dept = "GE"
def get_dept(self):
return self.__dept
def set_credits(self, credits):
if isinstance(credits, int) and credits > 0:
self.__credits = credits
else:
self.__credits = 3
def get_credits(self):
return self.__credits
students = []
def recordStudentEntry():
name = input('What is your name? ')
age = input('How old are you? ')
studentID= input('What is your student ID? ')
address = input('What is your address? ')
s1 = Student(name, studentID, int(age), address)
students.append(s1)
s1.add_course(recordCourseEntry())
print('\ndisplaying students...')
displayStudents()
print()
def recordCourseEntry():
courses = []
for i in range(2):
courseName = input('What is the name of one course you are taking? ')
dept = input('What department is your course in? ')
credits = input('How many credits is this course? ')
c1 = Course(courseName, dept, credits)
print(c1)
courses.append(c1)
displayCourses(courses)
return courses
def displayCourses(courses):
print('\ndisplaying courses of student... ')
for c in range(len(courses)):
print(courses[c])
def displayStudents():
for s in range(len(students)):
print()
print(students[s])
recordStudentEntry()
This is how the code above prints out the 'displaying students...' part:
displaying students...
Name: sam
Age: 33
Address: 123 st
Student ID:123abc
[<__main__.Course object at 0x000002BE36E0F7F0>, <__main__.Course object at
0x000002BE36E0F040>]
I know that it is printing out the location because I need to index into the list. However, the length of the list will be different every time. Normally if I wanted to index into a list, for example, to print a list of names, I would do:
listOfNames = ['sam', 'john', 'sara']
for i in range(len(listOfNames)):
print(listOfNames[i])
or
listOfNames = ['sam', 'john', 'sara']
for i in listOfNames:
print(i)
(not sure what if any difference there is between the 2 ways since they both print out the same way:)
sam
john
sara
How can I write something like the indexing into a list technique shown here in my str method for my class so that it prints the information and not the location?
It would be good to keep to the standard conventions for Python, such as naming
private attributes for objects with single underscores, not double underscores.
The latter are reserved for Python "internal" attributes and methods.
Also, it is convention to use object attributes for objects with get/set methods,
not class attributes. This will make it easier to inspect your objects, while
still maintaining data hiding. Example:
class Course:
def __init__(self, courseName, dept= 'GE', credits= None):
self._courseName = None
self._dept = None
self._credits = None
self.set_courseName(courseName)
...
Your question about why the courses don't print out the way you expected
is rooted in a programming error with the way you programmed the recording
of courses. In recordCourseEntry(), you record two courses and put them
in a list. However, you pass that to your Student object using a method
intended for one course at a time. My suggested fix would be:
...
# s1.add_course(recordCourseEntry())
courses = recordCourseEntry()
for course in courses:
s1.add_course(course)
...
This will probably be enough to get you going. An example output I got was:
Name: Virtual Scooter
Age: 33
Address: 101 University St.
Student ID:2021
ff/GE/3
gg/GE/3

automatically instantiating objects

players_list = [Ani, Paty, Felix, Alex]
class Player:
def __init__(self, name):
self.name = name
self.score = 0
self.vote = 0
self.player_hand = []
self.choice = ''
self.player_hand = []
def player_turn(self):
print(self.name, "'s turn")
def p_vote(self):
print(self.name, " voted")
I tried to iterate over the list, but it always gives me an error: NameError: name 'Ani' is not defined
for player in players_list:
player = Player(str(player))
But doing all the process manually work:
Ani = Player("Ani"), etc
Is there any way that i can automate this process?
First of all the thing you should know, the players_list that you have declared are not containing strings, they are being considered as variables which you have not defined anywhere, and therefore the NameError.
Now, if you want to correct this, and if you actually intend to store objects of Player in players_list, then you can do the following:
players_list = ["Ani", "Paty", "Felix", "Alex"]
class Player:
def __init__(self, name):
self.name = name
self.score = 0
self.vote = 0
self.player_hand = []
self.choice = ''
self.player_hand = []
def player_turn(self):
print(self.name, "'s turn")
def p_vote(self):
print(self.name, " voted")
for i in range(len(players_list)):
players_list[i]=Player(players_list[i])
This will store Player objects in the list you have declared just the thing that you expect to get.
You are having problems with the players not being defined. So players_list = [Ani, Paty, Felix, Alex] will throw an error because the objects Ani, Paty, Felizx, and Alex do not exist.
class Player:
def __init__(self, name):
self.name = name
self.score = 0
self.vote = 0
self.player_hand = []
self.choice = ''
self.player_hand = []
def player_turn(self):
print(self.name, "'s turn")
def p_vote(self):
print(self.name, " voted")
Now, we need to iterate through the list.
players_list = ['Ani', 'Paty', 'Felix', 'Alex']
players = [Player(player) for player in players_list]
Sounds like you're trying to dynamically create variables - write code that writes code.
You could try to use the exec built-in function.
players = ['Ani', 'Paty', 'Felix', 'Alex']
class Player:
def __init__(self, name):
self.name = name
def p_vote(self):
print(self.name + " voted.")
for player in players:
exec( "%s = Player( '%s' )" %(player, player) )
Ani.p_vote()
Although, general internet advice has two points to make:
Be cautious where you use exec.
The Pythonic way is to write out the variables, "explicit is better than implicit."

Python __iadd__ TypeError: unsupported operand type(s) for +=

I am building a battle simulation program. Running into TypeError. Unable to resolve. Any help would be appreciated. I can't seem to get the iadd function working. I'm trying to add new Pokemon objects to the existing PokemonTrainer object by using the iadd function in python. Anyone has any ideas on how to execute this?
main.py
name = input("State your name: ")
player = PokemonTrainer(name)
player += Pokemon("Staryu", ElementType.WATER, [
Move("water blast", ElementType.WATER, 5),
Move("water cyclone", ElementType.WATER, 6)
])
pokemon_trainer.py
For the iadd, I'm using type-based dispatch to deal with the parameter. If it is of type Pokemon, call the add_pokemon method on self and then return the self object. If it is of type Item, call the add_item method on self and then return the self object. Otherwise, raise a TypeError.
from pokemon import Pokemon
class PokemonTrainer:
def __init__(self, name, pokemon = [], items = [], current_active_pokemon = None):
self.name = name
self.pokemon = pokemon
self.items = items
self.current_active_pokemon = current_active_pokemon
def __iadd__(self, other):
self.pokemon.append(other)
if (type(other) == type(Pokemon)):
self.add_pokemon(other)
elif (type(other) == type(Item)):
self.add_item(other)
else:
raise TypeError("Only Pokemon or Item type are allowed")
return self
def add_pokemon(self, pkmn):
self.pokemon.append(pkmn)
if (self.current_active_pokemon == None):
self.current_active_pokemon = pkmn
def add_item(self, item):
self.items.append(item)
pokemon.py
class Pokemon:
def __init__(self, name, pokemon_type, moves):
self.name = name
self.pokemon_type = pokemon_type
self.moves = moves
self.level = 1
self.exp = 0
self.max_hp = 100
self.current_hp = self.max_hp
self.attack_power = 1
self.defense_power = 1
self.fainted = False
I guess you want isinstance(other, Pokemon) instead of type(other) == type(Pokemon).
https://docs.python.org/3/library/functions.html#isinstance
Also relevant here: What are the differences between type() and isinstance()?

Nested data in attribute is not accessible

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

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

Categories

Resources