Creating a list of class instances - python

I'm trying to make a simple library system as part of an intro to OOP in Python. I became stuck trying to fit books into my library class. I have made a first simple class, called Book, which makes books, can show their ID, name and price etc.
Now im trying to create the actual class called Library. I want to make a list of all the books in the library with their ID, cost and name. Here I encountered an isseu. I have no idea how to add the instances of the class Book to the list I made in Library, my code can be found down here.
class Library(object):
def __init__(self, book):
self.book = book
def add_item(self, book):
mylist.append(book)
return mylist
if __name__ == '__main__':
booklist = []
Book1 = Book(1, 'Bookname1', "$30")
Book2 = Book(2, 'Bookname2', "$10")
Book1.show()
Book1.get_attribute_string()
and the code for the books, which I would rather keep the same. Ofcourse im open to suggestions, but im not that well versed in OOP in Python yet so don't suggest things to complicated! Thanks.
class Book(object):
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)

The class Library needs an attribute e.g. books. In this list, all books are stored. You need to define it in the __init__method, so you can have multiple libraries with different books.
class Library:
def __init__():
self.books = []
def add_book(book):
self.books.append(book)
def get_books(self):
return self.books
def show_books(self):
# This is one possible example how you can print all books
for book in self.books:
print(str(book))
class Book:
pass # Your Book code
def __str__(self):
return f"{self.name}, {self.id}, {self.price}"
# Returns a string with all attributes of the string.
# Note that is a f-string, but you can use any string
# This function is called, when you call str(book1)
if __name__ == "__main__":
book1 = Book()
book2 = Book()
lib = Library()
lib.add_book(book1)
lib.add_book(book2)
lib.show_books()
all_books = lib.get_books()
also_all_books = lib.books

Your library class should probably contain a collection of books (or something more specific, depends on the details of your task). Here is an example:
class Library(object):
def __init__(self, book):
# collection of library books. This is the library state
self.books = tuple()
def add_item(self, book):
# add an item to the collection
self.books += (book, )
return self.books
I preferred to use an immutable collection so that others can't change the library state outside the class. But you can use a list. You just have to be careful.

Related

Initialising nested classes in Python

Let's say I want to create a class 'House' that has some attributes of its own, but also has a (nested?) 'Resident' class which has some attributes and has a mandatory attribute 'surname'. A house instance may exist though without any residents. How can create this so that I can eventually do the following?
myhouse = House()
residentX = myhouse.resident('Smith')
Currently I set this up as a nested class but run into trouble when I try and initialise myhouse given that it is requiring a surname at this point for the nested Resident class (which I don't necessarily have at this point)
class House:
def __init__(self):
self.someattribute = <someattribute>
self.resident = self.Resident()
class Resident:
def __init__(self, surname):
self.surname = surname
I know I can restructure the code to not use nested classes and then explicitly tie any resident to a house in my code. However, I would like to use the dot notation here (myhouse.resident) to automatically tie a resident to a house.
Also, I understand that nested classes in python are somewhat frowned upon - I'm open to suggestions on how to do the above in a more pythonic manner.
I would break out the Resident class and use a property/setter for .resident
Like this:
class House:
def __init__(self):
self.someattribute = <someattribute>
self._resident = None
#property
def resident(self):
return self._resident
#resident.setter
def resident(self, surname):
r = Resident(surname)
self._resident = r
class Resident:
def __init__(self, surname):
self.surname = surname
However, if you want .resident to be callable but also want to track the house's residents, you can still break out the Resident class, and use:
class House:
def __init__(self):
self.someattribute = <someattribute>
self.residents = []
def resident(self, surname):
'''
Add a resident to the house
'''
r = Resident(surname)
self.residents.append(r)
return r
class Resident:
def __init__(self, surname):
self.surname = surname

Generate classes based on a list of names in Python

The approach might be just wrong to begin with, but I'm trying to do the following:
class Material:
pass
class Vacuum(Material):
def __str__(self):
return 'vacuum'
class Aluminum(Material):
def __str__(self):
return 'aluminum'
class Graphite(Material):
def __str__(self):
return 'graphite'
class Beryllium(Material):
def __str__(self):
return 'beryllium'
I have different pieces of code that deals with different materials. Instead of passing a string as argument to that other pieces I would prefer to give it objects. This allows to have tab-completion with ipython and it is also a way to enforce the type.
To avoid changing the already written pieces, those will just do str(argument): if it is a string it recovers the old behavior, if it is one of the objects it will work.
The question is now: I want to support a given list of materials:
allowed_materials = ['vacuum', 'aluminum', 'graphite',]
and that list might be growing. Instead of manually writing the classes, how could I generate them based on the list?
You can define a metaclass that can generate your classes for you.
class mattype(type):
def __new__(mcls, name, bases=(), d=None):
def __str__(self):
return name.lower()
if not d:
d = {}
d['__str__'] = __str__
bases = (*bases, Material)
return super().__new__(mcls, name.title(), bases, d)
allowed_materials = ['vacuum', 'aluminum', 'graphite',]
classes = {name: mattype(name) for name in allowed_materials}
str(classes['vacuum']())
# 'vacuum'
If you do not need different class name for different material you can simply initialise it inside the material class. If not I will delete my answer.
class Material:
def __init__(self,name):
self.name=name
def __str__(self):
return self.name
allowed_materials = ['vacuum', 'aluminum', 'graphite',]
obj_lst=[Material(material) for material in allowed_materials]
for obj in obj_lst:
print(str(obj))
output:
vacuum
aluminum
graphite
I ended up doing the following, also adding objects to the module.
import sys
class Material:
def __str__(self):
return self.__class__.__name__
pass
print(sys.modules[__name__])
_materials = ['Copper', 'Vacuum']
for m in _materials:
setattr(sys.modules[__name__], m, type(m, (Material,), {})())

Learn Python, exercise 6.7

I'm reading Lutz & Ascher - Learn Python and I found this as a solution to one of the exercises:
class Lunch:
def __init__(self):
self.cust = Customer()
self.empl = Employee()
def order(self, foodName):
# start a Customer order simulation
self.cust.placeOrder(foodName, self.empl)
def result(self):
# ask the Customer what kind of Food it has
self.cust.printFood()
class Customer:
def __init__(self):
# initialize my food to None
self.food = None
def placeOrder(self, foodName, employee):
# place order with an Employee
self.food = employee.takeOrder(foodName)
def printFood(self):
# print the name of my food
print self.food.name
class Employee:
def takeOrder(self, foodName):
# return a Food, with requested name
return Food(foodName)
class Food:
def __init__(self, name):
# store food name
self.name = name
if __name__ == '__main__':
x = Lunch()
x.order('burritos')
x.result()
x.order('pizza')
x.result()`
What I don't understand is how the definition of the method placeOrder inside the customer class works, more specifically, there is no class employee (just Employee) whose method placeOrder could be used.
def placeOrder(self, foodName, employee):
# place order with an Employee
self.food = employee.takeOrder(foodName)
you may need to read a little bit about object oriented programming, and dynamic typing to grasp this. So basically, employee is an argument which will be passed at runtime, its type will be determined after the call to placeOrder. if you call PlaceOrder and put an instance of Employee or any class that has method takeOrder(), it will work. Imho, you should try to code an example from the beginning and test out what you learn, it will help you learn Python faster
`

Getting variable name in of some duck-type classes

I have a Language class as such:
class _Language:
def __init__(self, name, bRightToLeft=False):
self.name = name
self.bRightToLeft = bRightToLeft
def isRightToLeft(self):
return self.bRightToLeft
def getName(self):
return self.name
class Language:
EN = _Language("English")
AF = _Language("Afrikaans")
SQ = _Language("Albanian")
And I create a Language object as such:
l1 = Language.EN
After some processing with the english object, I would like to retrieve its "subtype", i.e. EN. For instance:
print l1
[out]:
EN
I have tried adding __repr__ or a __str__ in the Language class but I'm not getting EN when i print l1:
class Language:
EN = _Language("English")
AF = _Language("Afrikaans")
SQ = _Language("Albanian")
def __str__(self):
return self.__name__
[out]:
Language
How could I access the variable name such that when I print l1 I get EN?
Any individual _Language instance has no idea what two-letter name you have given it in the Language namespace (or anywhere else, for that matter). So you have two possibilities:
(1) Have each instance store that information, on pain of having to repeat yourself:
class _Language:
def __init__(self, name, code, rtl=False):
self.name = name
self.code = code.upper()
self.rtl = rtl
def __str__(self):
return self.code
# ...
class Language:
EN = _Language("English", "EN")
Of course, you can reduce the repetition by providing a class method on Language to create and register _Language instances, rather than creating them at class definition time:
class Language:
#classmethod
def add(cls, name, code, rtl=False):
settatr(cls, code, _Language(name, code, rtl))
Language.add("English", "EN")
Language.add("Afrikaans", "AF")
Language.add("Albanian", "SQ")
This is probably the solution I'd favor personally.
(2) Have your _Language.__str__ method search the Language namespace to find out what name it's known by there:
def __str__(self):
for k, v in Language.__dict__.iteritems():
if v is self:
return k
In this case, you could store the result so it only needs to be looked up once:
class _Language:
# ...
code = None
def __str__(self):
if self.code:
return self.code
for k, v in Language.__dict__.iteritems():
if v is self:
self.code = k
return k
As b4hand points out, __name__ is the name of the type, which obviously isn't what you want. Objects don't know the names of variables they've been assigned to. If you think about it, how could they? The same object could be assigned to 20 different variables, or none (maybe the only place it exists is as a member of a set).
Passing the codes into the _Language constructor, as in kindall's answer, is obviously the cleanest solution… but it requires some repetition. Which, besides being tedious, introduces an opportunity for errors—and the kind of stupid typo errors that are the most painful to debug.
So, is there a way we could solve that? Sure. Just add the codes after construction:
class _Language:
def __init__(self, name, bRightToLeft=False):
self.name = name
self.bRightToLeft = bRightToLeft
def isRightToLeft(self):
return self.bRightToLeft
def getName(self):
return self.name
def __str__(self):
return self.code
class Language:
EN = _Language("English")
AF = _Language("Afrikaans")
SQ = _Language("Albanian")
for code, language in inspect.getmembers(Language):
if code.isalpha() and code.isupper():
language.code = code
But, while we're at it, we could also dispense with all that repetition of _Language:
class Language:
EN = "English"
AF = "Afrikaans"
SQ = "Albanian"
for code, language in inspect.getmembers(Language):
if code.isalpha() and code.isupper():
_language = _Language(language)
_language.code = code
setattr(Language, code, _language)
Although I think this might be nicer if you either used a dict or an enum.Enum instead of a class full of nothing but class attributes.
Or, if you want to get fancy, there are all kinds of Enum recipes that show how to create a custom enum metaclass that will use the existing magic that lets Enum values know their names, and also let you cram other attributes into them as you do in the _Language class.
Overriding, __str__ is the right approach but self.__name__ is the class name and not the member name, which is why you always see "Language".
In order to do what you want, you'll need to do some metaprogramming.
One approach would be to pass the "short" names in as arguments to the constructor. A more hacky approach would be to use the internal dict of the Language class object.

list as Python class variable

I want a class "Library" which has a class variable "books" which is a list of all the books in the library. So may class begins
class Library:
books = []
Now I want to add a book to my collection, but I can find no syntax
to do so. The model I have in my head is something like
def addBook(self, book):
books.append(book)
which I would expect to call with something like from the main routine with something like
lib = Library()
b = Book(author, title)
lib.addBook(b)
However, I've not been able to find any way to do this. I always get an error with the "append" where I try to add the book to the list.
You should declare books as an instance variable, not a class variable:
class Library:
def __init__(self):
self.books = []
def addBook(self, book):
self.books.append(book)
so you can create an instance of Library:
lib = Library()
b = Book(...)
lib.addBook(b)
Notes:
For further information about self , you can read this post.
This assumes your Book class is implemented correctly.
class Library():
def __init__(self):
self.books = []
def addBook(self, book):
self.books.append(book)
look at this example for the initialization and the setter:
class Library:
def __init__(self):
self.books = []
def add(self, x):
self.books.append(x)

Categories

Resources