Remove/add object into list - python

Can my coding work? The program should search for the hotel in the list, and if it returns None, I can add a new Hotel into the list.
I need help on the str part especially.
class Hotel:
def __init__(self, name, address):
self._name = name
self._address = address
#property
def name(self):
return self._name
#property
def address(self):
return self._address
def __str__(self):
return "Hotel Name: {} Address: {}".format(self._name, self._address)
class TransportServices:
def __init__(self):
self._hotels = []
#self._bookings = [] # as you can see i have two other list in this class
#self._drivers = []
def searchHotel(self, name):
for h in self._hotels:
if h.name == name:
return h
return None
def addHotel(self, hotel):
h = self.searchHotel(hotel)
if h is None:
self._hotels.append(hotel)
return True
else:
return False
def __str__(self):
hotels = [str(h) for h in self._hotels]
return "Hotels\n{} ".format(self._hotels) # need help here
def main():
hotel = TransportServices()
hotel.addHotel(Hotel("123", "ABC"))
hotel.addHotel(Hotel("234", "QWE"))
print(hotel)
main()

The problem is in your
def searchHotel(self, name):
for h in self._hotels:
if h.name == name:
return h
return None
method. You're comparing h.name to name, but name is not a string (like h.name), it's a Hotel object.
Try instead
def searchHotel(self, hotel):
for h in self._hotels:
if h.name == hotel.name:
return h
# return None is not necessary here, it's the default.
or else expect a string to be passed here and then in addHotel do:
h = self.searchHotel(hotel.name)
Note also that defining __eq__ can be useful if two things should be logically the same. If any two Hotels share a name and an address, they should be the same hotel, so you can do
class Hotel:
def __eq__(self, other: 'Hotel') -> bool:
if not isinstance(other, self.__class__):
return False
return (self.name, self.address) == (other.name, other.address)
This has the benefit of making searchHotel much easier.
def searchHotel(self, hotel):
if hotel in self._hotels:
return hotel

it think because you are just printing the TransportServices object list, try to use the searchHotel function.
print(hotel.searchHotel("234"))

The problem is in your return statement.
Change
def __str__(self):
hotels = [str(h) for h in self._hotels]
return "Hotels\n{} ".format(self._hotels) # need help here
to
def __str__(self):
hotels = [str(h) for h in self._hotels]
return "Hotels\n{} ".format(hotels)

Related

Unable to create an array of Python objects from YAML

I am trying to instantiate an array of python objects from YAML, inside a struct. Outside of a struct I am able to do this easily, but it seems that the YAML BaseLoader is failing to recursively search the sub nodes of my object.
import yaml
import ruamel.yaml
class Person:
def __init__(self, name: str = 'JohnDoe'):
self.name = name
#classmethod
def from_yaml(cls, constructor, node):
for m in constructor.construct_yaml_map(node):
pass
if 'Name' in m:
name = m['Name']
return cls(name=name)
def __repr__(self):
return f'Person(name={self.name})'
class Car:
def __init__(self):
self.passengers = []
def add_person(self, person: Person = None):
self.passengers.append(person)
#classmethod
def from_yaml(cls, constructor, node):
for m in constructor.construct_yaml_map(node):
pass
inst = cls()
if 'Driver' in m:
inst.passengers = [m['Driver']]+inst.passengers
if 'Passengers' in m:
foo = m['Passengers']
print(f'm[\'Passengers\'] = {foo}')
for person in m['Passengers']:
inst.add_person(person)
return inst
def __repr__(self):
return f'Car(passengers={self.passengers})'
if __name__ == "__main__":
yaml = ruamel.yaml.YAML(typ='safe')
yaml.register_class(Person)
yaml.register_class(Car)
data = yaml.load("""
- !Person &0
Name: 'Paul'
- !Person &1
Name: 'George'
- !Person &3
Name: 'John'
- !Car
Driver: *0
Passengers: [*1]
- !Car
Driver: *3
Passengers:
- !Person &4
Name: 'Ringo'
""")
print(f'data = {data}')
the above code prints the following to the console on execution:
m['Passengers'] = []
m['Passengers'] = []
data = [Person(name=Paul), Person(name=George), Person(name=John), Car(passengers=[Person(name=Paul)]), Car(passengers=[Person(name=John)])]
where as I would expect the output to be
m['Passengers'] = [Person(name=George)]
m['Passengers'] = [Person(name=Ringo)]
data = [Person(name=Paul), Person(name=George), Person(name=John), Car(passengers=[Person(name=Paul), Person(name=George)]), Car(passengers=[Person(name=John), Person(name=Ringo)])]
no matter what, even with an array of strings, the associated value of the key 'Passengers' is always [] in the dictionary m.
do I have to manually tell the constructor to travers the rest of the node first in the from_yaml function, or does YAML loader work recursively from the bottom up?
There is no need to import yaml in your example.
During the construction of the car, its passengers are not yet know. So
what you need to do is construct potentially recursive data such
as Person and Car in a two step process, first constructing and yielding the "empty" Car
then filling in the Driver and any Passangers on the already yielded instance. The loader knows
how to handle this, so you don't have to recurse into anything in your from_yaml.
Additionally you'll need to call constructor.construct_mapping(node, deep=True)
in from_yaml, instead of your iteration over constructor.construct_yaml_map(node):
import ruamel.yaml
class Person:
def __init__(self, name: str = 'JohnDoe'):
self.name = name
#classmethod
def from_yaml(cls, constructor, node):
inst = cls()
yield inst
m = constructor.construct_mapping(node, deep=True)
if 'Name' in m:
inst.name = m['Name']
def __repr__(self):
return f'Person(name={self.name})'
class Car:
def __init__(self):
self.passengers = []
def add_person(self, person: Person = None):
self.passengers.append(person)
#classmethod
def from_yaml(cls, constructor, node):
inst = cls()
yield inst
m = constructor.construct_mapping(node, deep=True)
if 'Driver' in m:
inst.passengers = [m['Driver']] + inst.passengers
if 'Passengers' in m:
foo = m['Passengers']
print(f'm[\'Passengers\'] = {foo}')
for person in m['Passengers']:
inst.add_person(person)
def __repr__(self):
return f'Car(passengers={self.passengers})'
if __name__ == "__main__":
yaml = ruamel.yaml.YAML(typ='safe')
yaml.register_class(Person)
yaml.register_class(Car)
data = yaml.load("""
- !Person &0
Name: 'Paul'
- !Person &1
Name: 'George'
- !Person &3
Name: 'John'
- !Car
Driver: *0
Passengers: [*1]
- !Car
Driver: *3
Passengers:
- !Person &4
Name: 'Ringo'
""")
print(f'data = {data}')
which gives:
m['Passengers'] = [Person(name=George)]
m['Passengers'] = [Person(name=Ringo)]
data = [Person(name=Paul), Person(name=George), Person(name=John), Car(passengers=[Person(name=Paul), Person(name=George)]), Car(passengers=[Person(name=John), Person(name=Ringo)])]
Although it is allowed to write the tag followed by the anchor, it is IMO more appropriate to write the anchor
followed by the tag, because you'll get an anchored instance of the tagged object.
So that leaves me wondering what the name is of &2 !Person (probably the same as for !Person &2), is it Pete?
I was able to find a partial answer in this post.
In the case of raumel.yaml, it looks like our constructor is the from_yaml function, which gets added when the class is registered. All we have to do is add a yield after the initialization of our class in from_yaml, and before we retrieve our recursive item Passengers.
class Car:
def __init__(self):
self.passengers = []
def add_person(self, person: Person = None):
self.passengers.append(person)
#classmethod
def from_yaml(cls, constructor, node):
for m in constructor.construct_yaml_map(node):
print(f'm{type(m)} = {m}')
pass
inst = cls()
yield inst # <-- This yield statement fixes our issue
if 'Driver' in m:
inst.passengers = [m['Driver']]+inst.passengers
if 'Passengers' in m:
foo = m['Passengers']
print(f'm[\'Passengers\'] = {foo}')
for person in m['Passengers']:
inst.add_person(person)
return inst
def __repr__(self):
return f'Car(passengers={self.passengers})'

How to get all instances of a class

I am learning Python as a beginner and I'd like to create a class Person. In the constructor I would want to put every instance I create into a set called 'instances'. Then I'd like the instances() method to return all the instances. How can I do this?
class Person:
# Type annotations
__first_name: str
__last_name: str
instances: set
# Initializing variables
no_of_persons = 0
instances = set()
def __init__(self, firstname="unknown", lastname="unknown"):
self.__first_name = firstname
self.__last_name = lastname
Person.no_of_persons += 1
Person.instances.add() ## Here I have problems
#property
def first_name(self):
return self.__first_name
#first_name.setter
def first_name(self, firstname):
self.__first_name = firstname
#property
def last_name(self, ):
return self.__last_name
#last_name.setter
def last_name(self, lastname):
self.__last_name = lastname
def getFullName(self):
""" Returns a tuple of the firstname and the lastname """
return (self.__first_name, self.__last_name)
def summary(self):
""" Returns a dictionary of all instance variables """
return {'first_name': self.__first_name,
'last_name': self.__last_name}
#staticmethod
def number_of_persons():
return Person.no_of_persons
#staticmethod
def instances():
return Person.instances
p1 = Person()
Person.number_of_persons()
Person.instances()
You need to add self to the set.
Person.instances.add(self)
or more idiomatically
self.__class__.instances.add(self)
Also, you need to use a different name for the method that gets the instances; and it should be a classmethod, not a staticmethod.
#classmethod
def get_instances(cls):
return cls.instances
Although really you don't need a method here at all, as you can access Person.instances (the attribute) directly.

Python class doesn't see second object in list(attribute)

I want to code movie classification class, which can find (based on some criterias), add and print movies.
Here is my code:
class Movie:
def __init__(self,name,director,year,location):
self.name = name
self.director = director
self.year = year
self.location = location
self.information = {'name':self.name,'director':self.director,'year':self.year,'location':self.location}
def get_name(self):
return self.name
def get_director(self):
return self.director
def get_year(self):
return self.year
def get_location(self):
return self.location
def get_information(self):
return self.information
def __str__(self):
return f"Name = {self.name},director = {self.director},year = {self.year},location = {self.location}"
class Classification:
def __init__(self):
self.movie_list = []
def length(self):
return len(self.movie_list)
def __getitem__(self,key):
if isinstance(self.movie_list,slice):
return self.movie_list[key]
def add_movie(self,movie):
self.movie_list.append(movie)
def print_movie(self):
for movie in self.movie_list:
print(movie)
def find_movie(self,**kwargs):
check_list = []
for movie in self.movie_list:
for name,value in kwargs.items():
if movie.get_information()[name] == value:
check_list.append(True)
else:
check_list.append(False)
if all(item == True for item in check_list):
print(movie)
check_list.clear()
Here i have a class Movie and Classification; Classification has only 1 attribute, which is a list of movies. But i have two problems:
a = Movie('Matrix','Dan Yefimov','1999','New York')
b = Movie('Legend','Mak Markus','2005','Kiev')
clasif = Classification()
clasif.add_movie(a)
clasif.add_movie(b)
clasif.find_movie(location = 'New York')
find_movie() works for the first movie (a in our case) in the list. But for the second one it prints nothing, even when I enter correct parameters for a search.
Slicing doesn't work. There is no error message, it just prints nothing.
Can you help me with my problems?
P.S I would like also to hear some general advices about improving of my code.
By removing the unnecessary methods, we can shorten the code considerably. We can also just pass __getitem__ through to the list. I would also use __len__ over defining a length method.
class Movie:
def __init__(self,name,director,year,location):
self.name = name
self.director = director
self.year = year
self.location = location
def __str__(self):
return f"Name = {self.name},director = {self.director},year = {self.year},location = {self.location}"
class Classification:
def __init__(self):
self.movie_list = []
def __len__(self): # __len__ lets you do len(classif)
return len(self.movie_list)
def __getitem__(self,key):
return self.movie_list[key]
def add_movie(self,movie):
self.movie_list.append(movie)
def print_movie(self):
for movie in self.movie_list:
print(movie)
def find_movie(self,**kwargs):
for movie in self.movie_list:
if all(hasattr(movie, k) and getattr(movie, k) == v for k, v in kwargs.items()):
print(movie)
Here is a version of your code will a little less code:
class Movie:
ATTRIBUTES = ('name', 'director', 'year', 'location')
def __init__(self, name, director, year, location):
self.name = name
self.director = director
self.year = year
self.location = location
def __str__(self):
return ', '.join(
'{} = {}'.format(attr_name, getattr(self, attr_name))
for attr_name in self.ATTRIBUTES)
class Classification:
def __init__(self):
self.movie_list = []
def __len__(self):
return len(self.movie_list)
def __getitem__(self, key):
return self.movie_list[key]
def add_movie(self, movie):
self.movie_list.append(movie)
def print_movies(self):
for movie in self.movie_list:
print(movie)
def find_movies(self, **kwargs):
for movie in self.movie_list:
do_print = True
for attr_name, attr_value in kwargs.items():
if attr_name in Movie.ATTRIBUTES:
if getattr(movie, attr_name) != attr_value:
do_print = False
if do_print:
print(movie)
I added a class attribute called ATTRIBUTES to Movie; this is used in Movie.__str__() and also in Classification.find_movies(). It is a suggestion to avoid repeating the attributes a lot in the code.
In the method Classification.find_movies() I check that it is a valid attribute before I compare it to the movie instance. Invalid parameters are ignored, but you could change the code so that invalid parameters automatically cause the movies to not be printed (all will be excluded).

Printing a class instance results in None if I change the class member values

I have run into a problem I can't explain:
class A:
def __init__(self, rid, title):
self.rid = rid
self.title = title
self.b = []
def __add__(self, other):
if type(other) == B:
self.b += [other]
def __str__(self):
return self.rid + ' - ' + self.title
def __repr__(self):
return str(self)
class B:
def __init__(self, rid, title):
self.rid = rid
self.title = title
b = B('123', 'abc')
a = A('345', 'cde')
print(a)
a += b
print(a)
The first print results in the expected output:
345 - cde
However, the second print (after the adding of b) results in:
None
Why is that? I am not changing the rid or the title of a nor do I create new and uninitialized instance called a, or am I?
The expression a += b is a shorthand for: a = a.__add__(b)
As your __add__() method returns None that means you will assign None to a.

Python - How do I write a more efficient, Pythonic reduce?

I'm trying to build a very lightweight Node class to serve as a Python-based hierarchy search tool. See the definition below.
from functools import reduce
from operator import or_
class Node:
def __init__(self, name):
self.name = name
self.children = []
def add_child(self, child_node):
self.children.append(child_node)
def contains(self, other_node):
if self == other_node:
return True
elif other_node in self.children:
return True
else:
return reduce(or_, [child.contains(other_node)
for child in self.children], False)
def is_contained_by(self, other_node):
return other_node.contains(self)
def __eq__(self, other_node):
return self.name == other_node.name
def __de__(self, other_node):
return self.name != other_node.name
contains seems to be a textbook case of functional programming (pulled directly from Why Functional Programming Matters).
Question: is there a more efficient or Pythonic way of writing contains? I know that map is usually replaced by list comprehension, but I hadn't seen a better way of doing reduce-based recursion.
Thanks,
Mike
===EDITED ... HERE'S THE REDONE CLASS TAKING INTO ACCOUNT THE ANSWER AND COMMENTS===
class Node:
def __init__(self, name):
self.name = name
self.children = []
def add_child(self, child_node):
# Hattip to lazyr for catching this.
if self.contains(child_node) or child_node.contains(self):
raise TreeError('A relationship is already defined.')
else:
self.children.append(child_node)
def contains(self, other_node):
# Hattip to lazyr for pointing out any() and to Jochen Ritzel for
# eliminating the silly child check.
return (self == other_node or
any(child.contains(other_node) for child in self.children))
def is_contained_by(self, other_node):
return other_node.contains(self)
def __eq__(self, other_node):
return self.name == other_node.name
def __de__(self, other_node):
return self.name != other_node.name
def __repr__(self):
return self.name
I think (not tested) that you instead of reduce should use any like this, which will stop on the first hit:
return any(child.contains(other_node) for child in self.children)
By the way, did you mean for a.contains(b) to return False when a == b and len(a.children) > 0?
Edit: If your tree contains a loop, like this:
a = Node("a")
b = Node("b")
a.add_child(a)
a.add_child(b)
then
a.contains(b)
will crash the program. You may want to check for this either in contains or in add_child, depending on which you use the most.

Categories