Searching for an ID in a list of books - python

I've made a simple library system which stores books with an ID, name and cost. My question is rather simple but my limited knowledge of python has let me down.
I've created a class that stores books in the library, they are created like this;
if __name__ == '__main__':
lib = Library()
book1 = Book(1, 'Bookname1', "$30")
book2 = Book(2, 'Bookname2', "$10")
book3 = Book(3, 'Bookname3', "$40")
I have to make a function that searches for a book by its ID, by making a function in my library class. I tried to make it like in the code below, but it didn't work. basically, I want to give my function an ID, and it should return the name and cost of that particular book, but only if the ID is present in the list.
class Book:
def __init__(self, ID, name, price):
self.ID = ID
self.name = name
self.price = price
def show(self):
print(self.ID, self.name, self.price)
def get_attribute_string(self):
print(str(self.ID) + '_' + str(self.name) + '_' + str(self.price))
def get_id(self):
print(self.ID)
def get_name(self):
print(self.name)
def get_price(self):
print(self.price)
class Library:
def __init__(self):
self.books = []
def add_book(self, Book):
self.books.append(Book)
def remove_book(self, Book):
self.books.remove(Book)
#def show_id(self, ID):
# if ID in lib:
# return self.books
def show_all(self):
for Book in self.books:
Book.show()
if __name__ == '__main__':
lib = Library()
book1 = Book(1, 'Bookname1', "$30")
book2 = Book(2, 'Bookname2', "$10")
book3 = Book(3, 'Bookname3', "$40")
#1.show_id
lib.add_book(book1)
lib.add_book(book2)
lib.add_book(book3)
lib.remove_book(book2)
lib.show_all()

I think the simplest idea if you need ID indexing is to use a dictionary:
class Library:
def __init__(self):
self.books = dict()
def add_book(self, Book):
self.books[Book.ID] = Book
def remove_book(self, Book):
del self.books[Book.ID]
def get_book(self, ID):
return self.books[ID]
def show_id(self, ID):
self.get_book(ID).show()
def show_all(self):
for Book in self.books.values():
Book.show()
You could even rename get_book to __getitem__, this second name is special in python, it's called a dunder method (or magic method). Implementing it will allow you to write lib[id] instead of lib.show_id(id) (I'm not saying that you should, but that's an option). There are many other dunder methods that you can try using for fun, you can find some of them in the python data model.
I think that you should post your code on codereview as you may use broader advices on your code.

As noted above, there might be better implementations, but using your current code, you can do the below where I have adapted your show_id function according to requirements. It relies on a list comprehension to identify the correct IDs.
Hope this helps!
class Book:
def __init__(self, ID, name, price):
self.ID = ID
self.name = name
self.price = price
def show(self):
print(self.ID, self.name, self.price)
def get_attribute_string(self):
print(str(self.ID) + '_' + str(self.name) + '_' + str(self.price))
def get_id(self):
print(self.ID)
def get_name(self):
print(self.name)
def get_price(self):
print(self.price)
class Library:
def __init__(self):
self.books = []
def add_book(self, Book):
self.books.append(Book)
def remove_book(self, Book):
self.books.remove(Book)
def show_id(self, ID):
# simple list comprehension to filter; note that you might need to make sure that e.g. all IDs are int, or str
matching_ids = [book for book in self.books if book.ID == ID]
# return name, price tuple for all matching books as requested -- note that this will return a list of all matches
# if the IDs are truly unique, you can return e.g. a single tuple or Book object here
# if nothing found, will return empty list
return [(book.name, book.price) for book in matching_ids]
def show_all(self):
for Book in self.books:
Book.show()
if __name__ == '__main__':
lib = Library()
book1 = Book(1, 'Bookname1', "$30")
book2 = Book(2, 'Bookname2', "$10")
book3 = Book(3, 'Bookname3', "$40")
#1.show_id
lib.add_book(book1)
lib.add_book(book2)
lib.add_book(book3)
lib.remove_book(book2)
lib.show_all()
print(lib.show_id(1))

Even if this method is not really optimized, you can go through them with a for loop.
for book in lib.books:
if book.id == searched_id:
searched_book = book
break

Related

Python - displaying data when using classes

I am looking to work out how to get the name of the budget to appear in the output - the code creates a list (name of budget and budget amount) and appends these as list items to a main list. I realise importing the Budget class to the app file is the way of accessing functionality but I am wondering how to extract the data created within the app file so the repr
def __repr__(self):
return f"The budget is {self.balance}."
can return the name of the budget in the list
I have two files: budget_app.py and another budget_class.py
The app file uses the exec function to append new items to a list
from budget_class import Budget
list = []
def createBudget():
list2 = []
addbudgetname = input("Name the budget:")
exec1 = f"{addbudgetname} = Budget({int(input('How much to add to budget:'))})"
exec(exec1)
exec2 = f"list2.append({addbudgetname})"
exec(exec2)
return(list2)
list.append(createBudget())
list.append(createBudget())
for item in list:
print(item)
The class file initializes the Budget
class Budget():
class Budget():
def __init__(self, balance):
self.balance = balance
def __repr__(self):
return f"The budget is {self.balance}."
I am trying to work out a way of getting the name of the budget to appear in the output, which is currently
How much to add to budget:60
Name the budget:apples
How much to add to budget:800
[The budget is 60.]
[The budget is 800.]
The class structure is suboptimal, I would suggest to make a complete refactor, something as the following:
class Budget():
def __init__(self, balance=0, name=None, list2=[] ):
self.balance = balance
self.name = name
self.list2 = list2
def createBudget(self):
self.name = input("Name the budget:")
self.list2.append(input("How much to add to budget:"))
def add_to_balance(self, to_add):
self.balance += to_add
def __repr__(self):
return f"The budget is {self.balance}. and the name {self.name}"
budget1 = Budget(0,'name1',[1,2])
budget2 = Budget(4,'name2',[1,5])
budged_list = [budget1,budget2]
Now you can instantiate the class directly with the arguments or add them with your input support, you can also print the name etc.

Python project help (classes/expected type)

I am working on a project for school, simulating a payroll program, and I am getting an error. The error I am getting is
'Expected type 'Classification', got 'Employee' instead'. The relevant code is (I put *** around the code generating the error, it is the 5th function under the Employee Class).
class Employee:
def __init__(self, emp_id, first_name, last_name, address, city, state, zipcode, clas = None):
self.emp_id = emp_id
self.first_name = first_name
self.last_name = last_name
self.address = address
self.city = city
self.state = state
self.zipcode = zipcode
self.classification = clas
def make_hourly(self, hourly_rate):
self.clas = Hourly(hourly_rate)
self.classification = self.clas
def make_salaried(self, salary):
self.clas = Salaried(salary)
self.classification = self.clas
def make_commissioned(self, salary, rate):
self.clas = Commissioned(rate, salary)
self.classification = self.clas
def issue_payment(self):
***pay = Classification.compute_pay(self)***
print('Mailing', pay, 'to', self.first_name, self.last_name, 'at', self.address, self.city, self.state, self.zipcode)
class Classification(ABC):
''' Interface for employee classifications '''
#abstractmethod
def compute_pay(self):
pass
class Hourly(Classification):
''' Manages timecard info. Computes pay '''
def __init__(self, hourly_rate):
self.hourly_rate = hourly_rate
self.timecards = [] # A list of floats representing hours worked
def compute_pay(self):
for i in list_of_timecards:
if i[0] == self.emp_id:
self.timecards.extend(i[1:])
total = list(map(float, self.timecards))
total = sum(total)
self.timecards.clear()
return total * self.hourly_rate
def add_timecard(self, hours):
self.timecards.append(hours)
class Salaried(Classification):
def __init__(self, salary):
self.salary = salary
def compute_pay(self):
return self.salary / 24
class Commissioned(Salaried):
def __init__(self, salary, commission_rate):
self.commission_rate = commission_rate
self.salary = salary
self.receipts = []
def add_receipt(self, amount):
self.receipts.append(amount)
def compute_pay(self):
for i in list_of_receipts:
if i[0] == self.emp_id:
self.receipts.extend(i[1:])
total = list(map(float, self.receipts))
total = sum(total)
self.receipts.clear()
return (self.salary / 24) + ((self.commission_rate / 100) * total)
My understanding of the problem is that I need to pass my 'employee' object to the 'compute_pay' function, which then passes it to the relevant child class (hourly etc...) to run and return the result. I have tried changing
pay = Classification.compute_pay(self)
to
pay = Classification.compute_pay(self.clas)
however that returns error 'AttributeError: 'Employee' object has no attribute 'clas'
which makes no sense. Maybe it is that I am not assigning the employees to the class correctly?
The code for that is (it pulls from a CSV file, and it is pulling the data correctly and generating the class objects, I have checked)
def load_employees():
f = open("employees.csv")
f.readline() # skip header line
for line in f:
fields = line.strip().split(',')
emp = Employee(*fields[:7])
if fields[7] == '3':
clas = Hourly(fields[10]) # Need to define Hourly
emp.classification = clas
elif fields[7] == '2':
clas = Commissioned(fields[8], fields[9])
emp.classification = clas
elif fields[7] == '1':
clas = Salaried(fields[8])
emp.classification = clas
employees.append(emp)
I will figure out your line Classification.compute_pay(self):
Classification => the class Classification
compute_pay => class
method self => this = an Employee instance
pass means do nothing and is used to avoid unneccessary code.
Every class method has self as an argument to allow refering to this instance of the class.
To pass an argument (here your employee) use a parameter. Also implementing a method of the parent class overrides this method.
Every function compute_pay should have a second argument
def compute_pay(self, employee):
# do your stuff
And then you can use this line in issue_payment
pay = self.clas.compute_pay(self)
Two issues here,
Firstly, your Employee instance has two attributes: clas and classification. However, in your constructor, only classification is set.
def __init__(...
...
self.classification = clas
But self.clas is not set to anything. That's why you are getting that error 'Employee' object has no attribute 'clas'. It is only set when one of the make_hourly, make_salaried, or make_commissioned methods are invoked. So when you load the employees CSV, instead of manually creating the instance like you are doing here
clas = Hourly(fields[10])
you should be calling the method make_hourly on your emp instance, like so
emp.make_hourly(fields[10])
It's worth noting that fields[10] is terrible naming. Instead of unpacking all the fields at once, try to unpack them during the for loop:
for a, b, c, d in csv:
...
Secondly, this line of code is wrong in multiple ways
pay = Classification.compute_pay(self)
compute_pay is not a static function or a classmethod. So it shouldn't be called on the Classification class itself, but the Classification instance. This is what you stored in your self.clas attribute. So, compute_pay should be called on self.clas:
def issue_payment(self):
pay = self.clas.compute_pay()
...
In addition to that, when you call a method of a class from inside of another method in the same class, you don't ever need to pass the self argument. It is implied. So even if compute_pay was static or a class method, which it isn't, it would be called like so,
Classification.compute_pay()
Notice there is no self inside the parentheses. Similarly, when you call another method that is not static, self is never passed as an argument:
def my_method(self):
self.another_method()

Chaining class decorators to extend game item's abilities

I'm coding a little game for my python course, and I want to integrate an inventory and item system. The possibilities offered by the item are variables (weapons, quest item, consumable or not, an so on).
I have read this tutorial (in French) about the pattern decorator (google translated in english) and I came with this:
(I am using python3)
class Item(object):
def __init__(self, caracts=None, inventory=None):
self.caracts = {}
if caracts:
self.caracts = caracts
self.inventory = inventory
class ItemDecorator(Item):
def __init__(self, item):
super().__init__()
self.item = item
self.caracts = item.caracts
class Consumable(ItemDecorator):
def __init__(self, item, amount=1):
super().__init__(item)
self._amount = 0
self.amount = amount
#property
def amount(self):
return self._amount
#amount.setter
def amount(self, value):
self._amount = max(0, value)
if self._amount == 0 and self.item.inventory:
self.item.inventory.remove(self)
#amount.deleter
def amount(self):
del self._amount
class Usable(ItemDecorator):
def __init__(self, item, action=None, consumable=None):
if not action:
action = lambda *args, **kwargs: None
self._use = action
self.consumable = consumable
def use(self, *args, **kwargs):
if self.consumable and self.consumable.amount <= 0:
raise CantBeUsedException("There is no consumable")
else:
if self.consumable:
self.consumable.amount -= 1
self._use(*args, **kwargs)
My idea is to be able to do this:
potion = Usable(Consumable(Item(), 3), use_potion)
print("isinstance(potion.item, Consumable): {}".format(
isinstance(potion.item, Consumable)))
potion.consumable = potion.item
for dummy in range(4):
try:
potion.use()
except CantBeUsedException as e:
print("Expected exception: {}".format(e))
But here comes my issue, line 4. The consumable used by the usable potion should be potion itself. But potion lost its consumable ability and only potion.item has it. It's even worst, because the order in which I call my decorator matters. potion = Consumable(Usable(Item(), use_potion), 3) leads me to do potion.item.use(), always this item that annoys me.
How can I simplify this? Knowing that a usable doesn't necessarily consume itself, or even something. In fact, I would like to be able to do this, no matter which decorator was called first:
potion = Consumable(Usable(Item(), use_potion), 3)
potion.consumable = potion
potion.use()
I don't manage to found a clean solution for my issue. Here is all the questions that come to my mind:
* Is this Decorator pattern adapted? (It looks so to my mind, but I can be wrong)
* If it's not the case, to your mind, wouldn't be an interface system (thus, with multiple heritage) a better solution?
* What did I do wrong to get stuck here?
How can I make this system really simple while still being extensible. For this, I think about this solution:
class ItemDecorator(Item):
def __init__(self, item):
super().__init__()
self.item = item
self.caracts = item.caracts
if hasattr(item, "amount"):
self.amount = item.amount
if hasattr(item, "use"):
self.use = item.use
But by doing so, don't I lose all the extensibility of the Decorator pattern? Indeed, I would need to update ItemDecorator each time I want to create a quite complex decorator. Thus, wouldn't I lose all the advantage of the decorator pattern?
Thank you very much for your help
Right now your classes layout is rather twisted, and Usable(Consumable(Item(), 3), use_potion) doesn't look pythonic.
I'd slightly redesign the system:
class ItemCapability:
pass
class Consumable(ItemCapability):
def __init__(self, amount):
super().__init__()
self.amount = amount
class Usable(ItemCapability):
pass
class Item:
def __init__(self, name):
self.name = name
self.capabilities = {}
def make(self, capability):
assert isinstance(capability, ItemCapability)
assert capability.__class__ not in self.capabilities
self.capabilities[capability.__class__] = capability
def has(self, capability_cls):
try:
return self.capabilities[capability_cls]
except KeyError:
return False
potion = Item('potion')
potion.make(Usable())
potion.make(Consumable(amount=10))
print(potion.has(Usable))
print(potion.has(Consumable))
This way you have a very simple to understand class system, and easy way to query your items for capabilities.

How do I get the return value of my __str__() function to print in my main?

I need to create two classes for a program that takes in input variable for a book, title, author, number of pages and looks to see if it's checked out and then adds books to a dictionary in the following format dictionary[title] = author. Then I need to print al of my information in a main function in this format: ""title author pages checkedOut." I basically need to print out the return value of my __str__() function in my classes. Here is my code:
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
self.checkedOut = False
def checked_Out(self):
print(self.checkedOut)
return self.checkedOut
def change_value_of_checkedOut(self):
if self.checkedOut == False:
self.checkedOut = True
print("Switched from False to True.")
elif self.checkedOut == True:
self.checkedOut = False
print("Switched from True to False.")
def return_pages(self):
print(self.pages)
return self.pages
def return_title(self):
print(self.title)
return self.title
def __str__(self):
return ("Title: " + self.title + "Author: " + self.author +
"Pages: " + self.pages + "Checked Out Status: " +
self.checkedOut)
class Library:
def __init__(self):
self.collection = {}
def addExistingBook(self, book):
self.collection[book.title] = book.author
def addNewBook(self, title, author, pages):
new_book = Book(title, author, pages)
self.collection[title] = new_book.author
def change_checked_out_status(self, title):
if title in self.collection:
title.change_value_of_checkedOut()
else:
print("This book is not in the collection.")
def __str__(self):
for myBook in self.collection[myBook]:
self.collection[myBook]
self.collection[myBook] = self.author
# I want to print this return value in my main
return ("Title: " + self.title + "Author: " + self.author
+ "Pages: " + self.pages + "Checked Out Status: " + self.checkedOut)
def main():
title = str(input("Enter the title of the book. "))
author = str(input("Enter the author of the book. "))
pages = int(input("Enter the number of pages in the book. "))
myBook = Book(title, author, pages)
myLib = Library()
myLib.addExistingBook(myBook)
myLib2 = Library()
myLib3 = myLib2.__str__()
print(myLib3)
main()
It seems like something is wrong with the for loop in my __str__(self) function in the class Library (the idea is that I want the loop to iterate over ever book in the collection) but I'm not sure what the problem is. Here is the error message that I get:
Enter the title of the book. A Tale of Two Cities
Enter the author of the book. Charles Dickens
Enter the number of pages in the book. 434
Traceback (most recent call last):
File "C:\Python33\Class Programs\lab8.py", line 71, in <module>
main()
File "C:\Python33\Class Programs\lab8.py", line 68, in main
myLib3 = myLib2.__str__()
File "C:\Python33\Class Programs\lab8.py", line 54, in __str__
for myBook in self.collection[myBook]:
UnboundLocalError: local variable 'myBook' referenced before assignment
You are looping over the keys of a dictionary, you should not try to pass in a key in the loop statement:
def __str__(self):
for myBook in self.collection:
It is unclear what you are trying to do with that loop. You are then accessing self.collection[myBook] without doing anything with the return value, then you are replacing the value with self.author (which doesn't exist on self, that is still the library), then you return something completely different as the __str__() result.
I think you want to create a list of your library books here:
def __str__(self):
return ', '.join('{} - {}'.format(title, author) for title, author in self.collection.items())
Now you return a comma-separated list of all titles and authors in your collection.
There are 3 things going on there:
Using a generator expression, we loop over the dictionary .items() method; this generates (key, value) pairs for everything in the dictionary.
Each key and value is passed to the str.format() method, using the string as a template to put a - dash between the title and author pairs stored in your self.collection dictionary.
The whole loop is passed to the str.join() method; this method takes all the strings from the sequence given to it and concatenates them together with ', ' commas between them.
Next, you are creating an empty library, then print the output of the __str__() method; there is no need to call that explicitly, print() does that for you. Do not create a new library, just print the one you already have:
myLib = Library()
myLib.addExistingBook(myBook)
print(myLib)
I think you want to change
def __str__(self):
for myBook in self.collection[myBook]:
self.collection[myBook]
self.collection[myBook] = self.author
to
def __str__(self):
for myBook in self.collection.keys():
self.collection[myBook]
self.collection[myBook] = self.author

NameError: global name 'collection' is not defined

As an assignment, I'm trying to create two classes: One class, Book, looks to see if a book is checked out and returns the title, author and page numbers in a book (these are input variables) and the other class, called Library, adds title - author pairs to a dictionary and sees if some particular book is checked out or not. I keep getting an error message every time I try to run it. How do I fix this strange error?
Here is my code:
class Book:
def __init__(self, title, author, pages):
self.title = title
self.author = author
self.pages = pages
self.checkedOut = False
def checked_Out(self):
print(self.checkedOut)
return self.checkedOut
def change_value_of_checkedOut(self):
if self.checkedOut == False:
self.checkedOut = True
print("Switched from False to True.")
elif self.checkedOut == True:
self.checkedOut = False
print("Switched from True to False.")
def return_pages(self):
print(self.pages)
return self.pages
def return_title(self):
print(self.title)
return self.title
class Library:
def __init__(self):
collection = {}
def addExistingBook(self, book):
collection[book.title] = book.author
def addNewBook(self, title, author, pages):
new_book = Book(title, author, pages)
collection[title] = new_book.author
def change_checked_out_status(self, title):
if title in collection.keys():
title.change_value_of_checkedOut()
else:
print("This book is not in the collection.")
def main():
title = str(input("Enter the title of the book. "))
author = str(input("Enter the author of the book. "))
pages = int(input("Enter the number of pages in the book. "))
myBook = Book(title, author, pages)
myLib = Library()
myLib.addExistingBook(myBook)
main()
Here is what happens when I try to run it:
Enter the title of the book. The Count of Monte Cristo
Enter the author of the book. Alexandre Dumas
Enter the number of pages in the book. 1250
Traceback (most recent call last):
File "C:/Python33/Class Programs/book_library_classes.py", line 56, in <module>
main()
File "C:/Python33/Class Programs/book_library_classes.py", line 54, in main
myLib.addExistingBook(myBook)
File "C:/Python33/Class Programs/book_library_classes.py", line 36, in addExistingBook
collection[book.title] = book.author
NameError: global name 'collection' is not defined
You defined collection as a local variable in __init__:
def __init__(self):
collection = {}
But that doesn't magically make it an instance variable. You have to do that explicitly:
class Library:
def __init__(self):
self.collection = {}
def addExistingBook(self, book):
self.collection[book.title] = book.author
Also, I wouldn't make methods like this:
def return_title(self):
print(self.title)
return self.title
They're just another layer of obfuscation over the straightforward book.title attribute.
Also, you don't need to write .keys(). if key in dictionary is the preferred syntax:
if title in self.collection:
Add self.collection where ever you are referencing collection.
Class members must be accessed using self.propertyName. Your code should look like this:
class Library:
def __init__(self):
self.collection = {}
def addExistingBook(self, book):
self.collection[book.title] = book.author
....
In your Collection class you have to use internal collection:
class Library:
def __init__(self):
self.collection = {}
def addExistingBook(self, book):
self.collection[book.title] = book.author
Also the last line looks suspicious. Did you mean this:
self.collection[book.title] = book
collection is a local variable
Try referencing it in your functions with self.collection, which should fix your problem.
you should tell python that collection belongs to the Library instance, myLib:
modify the Library Class to say: self.collection everywhere you currently have collection
i.e.
class Library:
def __init__(self):
self.collection = {}
def addExistingBook(self, book):
self.collection[book.title] = book.author
def addNewBook(self, title, author, pages):
new_book = Book(title, author, pages)
self.collection[title] = new_book.author
def change_checked_out_status(self, title):
if title in self.collection.keys():
title.change_value_of_checkedOut()
else:
print("This book is not in the collection.")
Hope this helps!

Categories

Resources