Unable to create an array of Python objects from YAML - python

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})'

Related

Can i use 'self' in classes on its own?

I have a list of objects of the Person class. This list includes myself, so I need to remove "myself" from the list.
It means I need to remove the object from the list that calls this method.
class Population:
def __init__(self):
self.people = list()
class Person:
def __init__(self):
self.friends_list = list()
def make_friends(self, population, n):
temp_list = population.copy()
temp_list.remove(self)
self.friends_list.extend(random.sample(temp_list,n))
my test:
per = Person()
per2 = Person()
per3 = Person()
per4 = Person()
per5 = Person()
pop = [per,per2,per3,per4,per5]
for per in pop:
per.make_friends(pop, 2)
print('ME: ',per)
print(per.friends_list)
My tests run well, but there are general tests that check the code and they generate an error on this line:
try:
stud_res = person.make_friends(population, count)
except Exception:
print("\tPerson.make_friends() generated error")
return
Can I use self in this way, and if not, how can I better remove "myself" from the list?
It is a perfectly fine use case. By the way, note that you're overriding the builtin list.
To use self, you have to share a list collection between instances of a Person-class. In that case this collection should be declared as a class attribute or global list variable (not an instance attribute).
These samples of code are working:
with global list variable:
class Person:
def __init__(self, name):
self.name = name
def make_friends(self, list):
list.remove(self)
def __repr__(self):
return self.name
p1 = Person("Joe")
p2 = Person("Barack")
the_list = []
the_list.append(p1)
the_list.append(p2)
p1.make_friends(the_list)
print(the_list)
With class attribute:
class Person2:
class_list = []
def __init__(self, name):
self.name = name
Person2.class_list.append(self)
def make_friends(self):
Person2.class_list.remove(self)
def __repr__(self):
return self.name
p1 = Person2("Joe")
p2 = Person2("Barack")
print(Person2.class_list)
p1.make_friends()
print(Person2.class_list)
EDIT:
Variable 3 when a list of people is inside another class.
For accessing a list inside another class you could use attribute name or public method to get it if implemented:
class ClassWithList:
def __init__(self):
self.list_collection = []
def get_list(self):
return self.list_collection
class_with_list = ClassWithList()
class Person:
def __init__(self, name):
self.name = name
def make_friends(self, list):
list.remove(self)
def __repr__(self):
return self.name
p1 = Person("Joe")
p2 = Person("Barack")
# using implemented get-method of instance list attribute
class_with_list.get_list().append(p1)
class_with_list.get_list().append(p2)
print(class_with_list.get_list())
p1.make_friends(class_with_list.get_list())
print(class_with_list.get_list())
# instance list attribute of class`ClassWithList
print(class_with_list.list_collection)
p2.make_friends(class_with_list.list_collection)
print(class_with_list.list_collection)

error in returning __str__ from superclass

I'm getting an inaccurate output for my code. When it calls
super().__str__()
the output was
<__main__.Trip object at 0x00000251D30D5A30>
The output should be
Driver Id: 1 Name: John Contact: 82121355
from datetime import datetime
class Driver:
_nextId = 1
def __init__(self,name,contact):
self._name = name
self._contact = contact
self._driverId = Driver._nextId
Driver._nextId += 1
#property
def driverId(self):
return self._driverId
#property
def name(self):
return self._name
#property
def contact(self):
return self._contact
#contact.setter
def contact(self, newContact):
self._contact = newContact
def __str__(self):
return f'Driver Id: {self._driverId} Name: {self._name} Contact: {self._contact}'
class Trip:
def __init__(self,tripDate,startPoint,destination,distance,driver):
self._tripDate = tripDate
self._startPoint = startPoint
self._destination = destination
self._distance = distance
self._driver = driver
#property
def tripDate(self):
return self._tripDate
#property
def destination(self):
return self._destination
#property
def driver(self):
return self._driver
def __str__(self):
return f'{self._tripDate}, From: {self._startPoint} To: {self._destination}\n Distance: {self._distance}km' + super().__str__()
if __name__ == '__main__':
d1 = Driver('John','82121355')
t1 = Trip(datetime(2021,5,30,17,45),'Empire State Building','Rockerfeller Centre','2.25',d1)
print(t1)
The problem in your Trip's code:
def __str__(self):
return f'...' + super().__str__()
is that Trip is not a subclass of Driver nor does it inherit anything from Driver. The super call will not call Driver's __str__ method, but the default/built-in object.__str__(self) for all Python classes:
>>> class XYZ: pass
...
>>> obj1 = XYZ()
>>> print(obj1)
<__main__.XYZ object at 0x10e28b040>
The super() only works if your class is a subclass of another:
>>> class Animal:
... def __str__(self):
... return 'Animal __str__'
...
>>> class Dog(Animal):
... def __str__(self):
... return f'{super().__str__()} + Dog __str__'
...
>>> d = Dog()
>>> print(d)
Animal __str__ + Dog __str__
I don't know why you expect Trip's superclass to be Driver, because trips are not drivers, rather a Trip involves a Driver, so your current implementation where you instantiate a trip with a driver makes sense.
The only thing you need to change is to replace super() with self._driver which is an instance of Driver that you pass to Trip.
# super().__str__() --> self._driver.__str__()
def __str__(self):
return f'{self._tripDate}, From: {self._startPoint} To: {self._destination}\n Distance: {self._distance}km' + self._driver.__str__()
2021-05-30 17:45:00, From: Empire State Building To: Rockerfeller Centre
Distance: 2.25kmDriver Id: 1 Name: John Contact: 82121355
or more simply:
# super().__str__() --> str(self._driver)
# And put it inside the f-string
def __str__(self):
return f'{self._tripDate}, From: {self._startPoint} To: {self._destination}\n Distance: {self._distance}km {str(self._driver)}'
2021-05-30 17:45:00, From: Empire State Building To: Rockerfeller Centre
Distance: 2.25km Driver Id: 1 Name: John Contact: 82121355
since Python's str(obj) calls that object's __str__ method.

How do we call a class without passing variables on the call?

Here is a MRE:
class Dog():
def __init__(self, action, name):
self.action = action
self.name = name
def name_dog(self):
my_name = input("give me a name: ")
return self.name(my_name)
def act_like_dog(self):
order = input("Tell me to do something: ")
msg = self.name + " is " + order
return self.action(msg)
def main(self):
self.name_dog()
self.act_like_dog()
Dog.main()
The main objective for calling the dog class in this case is for the user to input their own values (name, action) for the class. So the variables for the class I specified (Dog class) are obtained from a function call (self.name_dog and self.act_like_dog) in the main() function, which prompts the user to give his/her input. However when I tried calling the main() function without self, I got an error saying a positional argument is needed, while if I called the function self(), an error says that self is not defined. How do I fix this?
You seem to be confused about a few elements.
You need to create an actual instance of the class to be able to class its methods - Dog(...). Apart from that, there is a logical problem where you expect to create an instance with name and action but then take them as inputs from the user. I think what you should do is simply set the attributes with user input in the __init__. Something like:
class Dog():
def __init__(self):
self.name = self.name_dog()
self.action = self.act_like_dog()
def name_dog(self):
my_name = input("give me a name: ")
return my_name
def act_like_dog(self):
order = input("Tell me to do something: ")
msg = self.name + " is " + order
return msg
dog = Dog()
If you want to be able to change the name and action later, you can use your main idea:
class Dog():
def __init__(self):
main()
...
def main():
self.name = self.name_dog()
self.action = self.act_like_dog()
dog = Dog()
You might be looking for something like so:
class Dog:
def __init__(self, action=None, name=None):
if name:
self.name = name
else:
self.name = input("Dog's name: ")
if action:
self.action = action
else:
self.action = input("Dog's action: ")
d = Dog()
# ... or ...
d = Dog("Max", "Bark")
# ... or ...
d = Dog("Max")
d = Dog(name="Max")
# ... or ...
d = Dog(action="Bark")

Remove/add object into list

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)

python prototype structure, not working

whenever i print the c1 object, it prints:
<main.Car object at 0x7fde8b29a240>
however i added the str method, to format it to a proper string, why is it not printing a readable string?
import copy
class Prototype:
def __init__(self):
# constructor method to create the object
self._objects = {}
def register_object(self, name, obj):
# this method is used to register an object
self._objects[name] = obj
def unregister_object(self, name):
# this method is used to unregister an object
del self._objects[name]
def clone(self, name, **attr):
obj = copy.deepcopy(self._objects.get(name))
obj.__dict__.update(attr)
return obj
class Car:
def __init__(self):
self.name = "Skylark"
self.color = "blue"
self.options = "extra horsepower in engine"
def __str__(self):
return '{} | {} | {}'.format(self.name, self.color, self.options)
c = Car()
prototype = Prototype()
prototype.register_object('skylark',c)
c1 = prototype.clone('skylark')
print(c1)
There is a problem with the indentation in your code. I've corrected this and can get the desired answer too. The indentation is a bit off for the function defs. in both the classes.
I've called this file as test.py
import copy
class Prototype:
def __init__(self):
# constructor method to create the object
self._objects = {}
def register_object(self, name, obj):
# this method is used to register an object
self._objects[name] = obj
def unregister_object(self, name):
# this method is used to unregister an object
del self._objects[name]
def clone(self, name, **attr):
obj = copy.deepcopy(self._objects.get(name))
obj.__dict__.update(attr)
return obj
class Car:
def __init__(self):
self.name = "Skylark"
self.color = "blue"
self.options = "extra horsepower in engine"
def __str__(self):
return '{} | {} | {}'.format(self.name, self.color, self.options)
c = Car()
prototype = Prototype()
prototype.register_object('skylark',c)
c1 = prototype.clone('skylark')
print(c1)
When I run the file
$ python test.py
The output is:
#Output: Skylark | blue | extra horsepower in engine

Categories