Dumping custom class objects to a YAML file - python-3.x

I would like some help on dumping a custom class object to a YAML file. A representation of my class looks like below:
from classes.enum_classes import Priority, Deadline
class Test(yaml.YAMLObject):
yaml_tag = u'!RandomTestClass'
def __init__(self):
self._name = ""
self._request_id = ""
self._order = None
self._priority = Priority.medium
self._deadline = Deadline.label_c
The last two parameters are objects of different classes, both of which are Enum derived classes. I am trying to dump the contents of an object of Test class to a YAML output file. My __repr__ method for the Test class looks so:
def __repr__(self):
return "(name=%r, request=%r, order=%r, priority=%r, deadline=%r)" % \
((str(self.name) + str(self.order)), self.request_id, self.order,
self.priority.name, self._deadline.name)
Working off the constructors and representers section of the PyYAML documentation and the example provided there (especially considering that the authors use lists for some of the class variables), I would expect my YAML file to display the actual display tags in the __repr__ method, rather than the variable names themselves. This is what I see currently:
--- !ContainerClass
_requests: !!python/object/apply:collections.defaultdict
args:
- !!python/name:builtins.list ''
dictitems:
'108':
- !RandomTestClass
_deadline: &id002 !Deadline '3'
_name: '108.1'
_order: '1'
_priority: &id006 !Priority '1'
_request_id: '108'
- !RandomTestClass
_deadline: &id002 !Deadline '3'
_name: '108.2'
_order: '2'
_priority: &id006 !Priority '1'
_request_id: '108'
_name: TestContainer
I want it to look like so:
---
Requests:
- name: '108.1'
- requestID: '108'
- priority: '1'
- order: '1'
- deadline: '3'
- name: '108.2' <for the second entry in the list and so on>
Name: ContainerClass
No amount of fiddling around with the __repr__ method or anything else has resulted in the output I would like. So there are two issues I would love to get some help with.
How do I get a sane, readable representation? I am guessing I will have to write a custom representer, so if someone could guide me with some pointers, since I was not able to find much information on that any way.
Getting rid of those pointers, or whatever we would want to call them next to priority and deadline. Priority and Deadline are the two classes referenced in my __init___ method above, that are Enum subclasses. Since they are already subclasses, any attempts to subclass them to yaml.YAMLObject result in an error with mixins. To get around that, some posts suggested I do so:
class Priority(Enum):
low = 0
medium = 1
high = 2
class Deadline(Enum):
label_a = 0
label_b = 1
label_c = 2
label_d = 3
def priority_enum_representer(dumper, data):
return dumper.represent_scalar('!Priority', str(data.value))
def deadline_enum_representer(dumper, data):
return dumper.represent_scalar('!Deadline', str(data.value))
yaml.add_representer(Deadline, deadline_enum_representer)
yaml.add_representer(Priority, priority_enum_representer)
Any information/pointers on solving these two issues will be much much appreciated. Apologies for the long post, but I have learnt that more information generally leads to much more precise help.
UPDATE:
My YAML file is written based on a list of these RandomTestClass objects that are stored in a defaultdict(list) in a ContainerClass.
class ContainerClass(yaml.YAMLObject):
yaml_tag = u'ContainerClass'
def __init__(self):
self._name = ""
self._requests = defaultdict(list)
def __repr__(self):
return "(Name=%r, Requests=%r)" % \
(self._name, str(self._requests))
#property
def requests(self):
return self._requests
#requests.setter
def requests(self, new_req, value=None):
if type(new_req) is dict:
self._requests = new_req
else:
try:
self._requests[new_req].append(value)
except AttributeError:
# This means the key exists, but the value is a non-list
# entity. Change existing value to list, append new value
# and reassign to the same key
list_with_values \
= [self._requests[new_req], value]
self._requests[new_req] = list_with_values
The ContainerClass holds instances of Test objects. In another class, which is the entry point for my code containing __main__, I create multiple instances of Test objects, that are then stored in an ```ContainerClass`` object and dumped out to the YAML file.
# requisite imports here, based on
# how the files are stored
from classes.Test import Test
from classes.ContainerClass import ContainerClass
class RunTestClass:
if __name__ == '__main__':
yaml_container = ContainerClass()
test_object_a = Test()
test_object_a._name = '108.1'
test_object_a._order = 1
test_object_a._request_id = '108'
yaml_container._name = "TestContainer"
yaml_container._requests[test_object_a._request_id] = test_object_a
test_object_b = Test()
test_object_b._name = '108.2'
test_object_b._order = 2
test_object_b._request_id = '108'
yaml_container._name = "TestContainer"
yaml_container._requests[test_object_b._request_id] = test_object_b
with open(output_file, mode='w+') as outfile:
for test_class_object in yaml_container._requests:
yaml.dump(test_class_object, outfile, default_flow_style=False,
explicit_start=True, explicit_end=True)
UPDATE:
Adding a single, consolidated file to the question, executable to replicate the issue.
import yaml
from enum import Enum
from collections import defaultdict
class Priority(Enum):
low = 0
medium = 1
high = 2
class Deadline(Enum):
label_a = 0
label_b = 1
label_c = 2
label_d = 3
def priority_enum_representer(dumper, data):
return dumper.represent_scalar('!Priority', str(data.value))
def deadline_enum_representer(dumper, data):
return dumper.represent_scalar('!Deadline', str(data.value))
yaml.add_representer(Deadline, deadline_enum_representer)
yaml.add_representer(Priority, priority_enum_representer)
class Test(yaml.YAMLObject):
yaml_tag = u'!RandomTestClass'
def __init__(self):
self._name = ""
self._request_id = ""
self._order = None
self._priority = Priority.medium
self._deadline = Deadline.label_c
#property
def name(self):
return self._name
#name.setter
def name(self, name):
self._name = name
#property
def request_id(self):
return self._request_id
#request_id.setter
def request_id(self, r_id):
self._request_id = r_id
#property
def order(self):
return self._order
#order.setter
def order(self, order):
self._order = order
#property
def priority(self):
return self._priority
#priority.setter
def priority(self, priority):
self._priority = priority
#property
def deadline(self):
return self._deadline
#deadline.setter
def deadline(self, deadline):
self._deadline = deadline
def __str__(self):
return self.name + ", " + self._request_id + ", " + str(self.order) + ", " \
+ str(self.priority) + ", " + str(self.deadline)
class ContainerClass(yaml.YAMLObject):
yaml_tag = u'ContainerClass'
def __init__(self):
self._name = ""
self._requests = defaultdict(list)
def __repr__(self):
return "(Name=%r, Requests=%r)" % \
(self._name, str(self._requests))
#property
def name(self):
return self._name
#name.setter
def name(self, name):
self._name = name
#property
def requests(self):
return self._requests
def set_requests(self, new_req, value=None):
if type(new_req) is dict:
self._requests = new_req
else:
try:
self._requests[new_req].append(value)
except AttributeError:
# This means the key exists, but the value is a non-list
# entity. Change existing value to list, append new value
# and reassign to the same key
print("Encountered a single value, converting to a list and appending new value")
list_with_values \
= [self._requests[new_req], value]
self._requests[new_req] = list_with_values
yaml_container = ContainerClass()
yaml_container.name = "TestContainer"
test_object_a = Test()
test_object_a._name = '108.1'
test_object_a._order = 1
test_object_a._request_id = '108'
yaml_container.set_requests(test_object_a.request_id, test_object_a)
test_object_b = Test()
test_object_b._name = '108.2'
test_object_b._order = 2
test_object_b._request_id = '108'
yaml_container.set_requests(test_object_b.request_id, test_object_b)
with open('test.yaml', mode='w+') as outfile:
yaml.dump(yaml_container, outfile, default_flow_style=False,
explicit_start=True, explicit_end=True)

There are different ways to solve this. Generally, you want to define a classmethod to_yaml in ContainerClass that will be called by the dumper, and implement the transition from native structure to YAML node graph there.
A minimal solution would be to just construct the structure you want to have as normal dict with a list in it, and tell the dumper to use that instead of the real structure:
#classmethod
def to_yaml(cls, dumper, data):
rSeq = []
for value in data._requests.values():
for t in value:
rSeq.extend([
{"name": t._name},
{"requestID": t._request_id},
{"priority": t._priority},
{"order": t._order},
{"deadline": t._deadline}
])
return dumper.represent_mapping("tag:yaml.org,2002:map",
{"Requests": rSeq, "Name": data._name})
This will give you
---
Name: TestContainer
Requests:
- name: '108.1'
- requestID: '108'
- priority: &id001 !Priority '1'
- order: 1
- deadline: &id002 !Deadline '2'
- name: '108.2'
- requestID: '108'
- priority: *id001
- order: 2
- deadline: *id002
...
YAML generates anchors & aliases for priority and requestID because the values refer the same objects. You can avoid those by doing
yaml.Dumper.ignore_aliases = lambda *args : True
before you dump the YAML.
You can be more sophisticated and iterate the properties instead of hard coding the names, but that is beyond the scope of this answer. If you want to load this YAML again into the same structure, you will need to add another classmethod from_yaml implementing the reverse transformation.

Related

Instantiation of objects crashes the kernel

I have tried creating instances of the class eachItem from a csv file, however, my code keeps crashing.
class eachItem():
pattern_iphone_7 = '.*[iI]*[pP]hone 7'
list_of_all_objects = []
def __init__(self, item_name, item_price, item_link, item_image, item_location):
self.dictionary = {}
self.dictionary['name'] = item_name
self.dictionary['price'] = item_price
self.dictionary['link'] = item_link
self.dictionary['image'] = item_image
self.dictionary['location'] = item_location
print(self.dictionary)
#self.__class__.list_of_all_objects.append(self.dictionary)
if re.match(self.__class__.pattern_iphone_7, self.dictionary['name']):
print(self.dictionary['name'])
item_iPhone_7(
self.dictionary['name'],
self.dictionary['price'],
self.dictionary['link'],
self.dictionary['image'],
self.dictionary['location'])
else:
self.__class__.list_of_all_objects.append(self.dictionary)
#classmethod
def from_csvfile(cls):
with open("scraped facebook marketplace data.csv", "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
items = list(reader)
i=0
for item in items:
print(f"Instantiated instance number: {i}")
i+= 1
eachItem(
item_name = item.get('name'),
item_price = item.get('price'),
item_link = item.get('link'),
item_image = item.get('image'),
item_location = item.get('location')
)
def __repr__(self):
return f"name: {self.dictionary['name']}, price: {self.dictionary['price']}, link: {self.dictionary['link']}, image: {self.dictionary['image']}, location: {self.dictionary['location']}"
eachItem.from_csvfile()
class item_iPhone_7(eachItem):
list_of_instances=[]
def __init__(self, item_name, item_price, item_link, item_image, item_location):
super().__init__(item_name, item_price, item_link, item_image, item_location)
item_iPhone_7.list_of_instances.append(self.dictionary)
if __name__ == "__main__":
eachItem.from_csvfile()
I am trying to do it this way because as I am trying to create instances of the class eachItem, I would also want to automatically create instances of the class item_iPhone_7. That is the reason why I try to create instance of the child class in the parent class.
How could I try to do this in a safer way, without crashing the kernel?

PYTHON: How to access class object's members if the object is a key in a dictionary

I have the following class:
class car_class(object):
def __init__(self, mileage=11, tyre_size=11):
self.mileage = mileage
self.tyre_size = tyre_size
self.default_val = ''
def __hash__(self):
return hash((self.mileage, self.tyre_size))
def __getitem__(self, default_val):
return self.default_val
def __setitem__(self, default_val, mileage, tyre_size):
self[default_val] = str(mileage) + '_' + str(tyre_size)
def __eq__(self, other):
return (self.mileage, self.tyre_size) == (other.mileage, other.tyre_size)
def __str__(self):
return ('dict_cl: (tyre_size=\'%d\', mileage=\'%d\'' % (int(self.tyre_size), int(self.mileage)))
def __repr__(self):
return ('dict_cl: (tyre_size=\'%d\', mileage=\'%d\'' % (int(self.tyre_size), int(self.mileage)))
def __ne__(self, other):
# Not strictly necessary, but to avoid having both x==y and x!=y
# True at the same time
return not(self == other)
I also have a dictionary which takes the object of this class as the Key against a value as follows-
my_dict = dict()
dict_value_list = list()
mm_dict_cl = car_class()
mm_dict_cl.mileage = 29
mm_dict_cl.tyre_size = 265
dict_value_list.extend(['car_color'])
my_dict.update(mm_dict_cl = dict_value_list)
So the dictionary(my_dict)key has the class object(mm_dict_cl) as key and car_color as a value for this key. The Key is the class object itself having two attributes mileage and tyre_size.
Now when I print the following value of the dictionary I get the value as car_color as expected -
`>>>` print(my_dict[next(iter(my_dict))])
['car_color']
However I'm struggling to find a way to retrieve the properties of the class object.
>>>` print(next(iter(my_dict)))
mm_dict_cl
It prints the class name and the key type if printed as string.
`>>>` print(type(next(iter(my_dict))))
<type 'str'>
Query: How can then I access the object attributes of the key?
I want to check what is the value of tyre_size and mileage for a particular 'car_color' using the key of the dictionary my_dict
Please help, a novice here, trying to learn this language.
-edit: Fixed the extend call to the list for adding 'car_colour' as a list instead of a string as pointed by SorousH Bakhtiary.
The problem here is that you stored the name of the instance of your class as key in your dictionary("it is string"), so you have no control over the instance but because your object is defined in global namespace, you can retrieve it like this : (I simplified your class for the moment)
class car_class:
def __init__(self, mileage=11, tyre_size=11):
self.mileage = mileage
self.tyre_size = tyre_size
self.default_val = ''
my_dict = {}
dict_value_list = []
mm_dict_cl = car_class()
dict_value_list.append('car_color')
print(dict_value_list)
mm_dict_cl.mileage = 'temp1'
mm_dict_cl.tyre_size = 'temp2'
my_dict.update(mm_dict_cl=dict_value_list)
# here :
obj = globals().get(next(iter(my_dict)))
print(obj.mileage) # prints temp1
print(obj.tyre_size) # prints temp2
btw, you should have used append instead of extend method to add 'car_color'

Can we skip explicit object creation in Python

When I do not crate object for CP class, the operations are not captured. I am referring to the code below, Can somebody help me understand why we need obj creation in this case
from abc import ABC, abstractmethod
class P(ABC):
def __init__(self):
super().__init__()
self._pre_map = {}
self._pre_order = []
def set_pre(self, tag_value):
index = len(self._pre_map)
print(index)
self._pre_map[index] = tag_value
self._pre_order.append(index)
def execute(self):
pass
class CP(P):
def __init__(self):
super().__init__()
def execute(self):
self.prnt()
def prnt(self):
print (self._pre_map)
print (self._pre_order)
#Working
print("\n++++++++ working")
obj = CP()
obj.set_pre("test string added")
obj.execute()
#Not Working
print("\n+++++++ not working")
CP().set_pre("test string added")
CP().execute()
It produces,
++++++++working
0
{0: 'test string added'}
[0]
+++++++not working
0
{}
[]
When you call the class the second time with CP.execute(), you have created a completely new instance of the CP class. It is not going to have the text string you specified.
If you actually wanted it to print the values like the working one you can make the functions return self after each call in the P class. If you did that you could do something like this.
from abc import ABC, abstractmethod
class P(ABC):
def __init__(self):
super().__init__()
self._pre_map = {}
self._pre_order = []
def set_pre(self, tag_value):
index = len(self._pre_map)
print(index)
self._pre_map[index] = tag_value
self._pre_order.append(index)
##need to return self here
return self
def execute(self):
pass
class CP(P):
def __init__(self):
super().__init__()
def execute(self):
self.prnt()
def prnt(self):
print (self._pre_map)
print (self._pre_order)
#Working
print("\n++++++++ working")
obj = CP()
obj.set_pre("test string added")
obj.execute()
#Not Working
print("\n+++++++ not working: but now working after returning self in the P class")
CP().set_pre("test string added").execute()
++++++++ working
0
{0: 'test string added'}
[0]
+++++++ not working: but now working after returning self in the P class
0
{0: 'test string added'}
[0]
This would print the result you want.
The reason for the difference is the fact that in the first one, you are creating an instance, and using that instance the whole way through, whereas in the second one, you are using two different instances of your class.
The two different instances cannot share their attributes, so you are unable to recall what happened. If you really don't want to use a dedicated variable, change your P class to look like this:
class P(ABC):
...
def set_pre(self, tag_value):
index = len(self._pre_map)
print(index)
self._pre_map[index] = tag_value
self._pre_order.append(index)
return self
...
And use CP().set_pre("test string added").execute()

In OOP in python, are different instances of an object when initialised with a default value the same?

I am trying to understand object oriented programming. I am doing this by creating a small poker like program. I have come across a problem whose minimal working example is this:
For this code:
import random
class superthing(object):
def __init__(self,name,listthing=[]):
self.name = name
self.listthing = listthing
def randomlyadd(self):
self.listthing.append(random.randint(1,50))
def __str__(self):
return '\nName: '+str(self.name)+'\nList: '+str(self.listthing)
Aboy = superthing('Aboy')
Aboy.randomlyadd()
print(Aboy)
Anotherboy = superthing('Anotherboy')
Anotherboy.randomlyadd()
print(Anotherboy)
I expect this output :
Name: Aboy
List: [44]
(some number between 1 and 50)
Name: Anotherboy
List: [11]
(again a random number between 1 and 50)
But what I get is:
Name: Aboy
List: [44]
(Meets my expectation)
Name: Anotherboy
List: [44,11]
(it appends this number to the list in the previous instance)
Why is this happening? The context is that two players are dealt a card from a deck. I am sorry if a similar question exists, if it does, I will read up on it if you can just point it out. New to stack overflow. Thanks in advance.
For the non minimal example, I am trying this:
import random
class Card(object):
def __init__(self, suit, value):
self.suit = suit
self.value = value
def getsuit(self):
return self.suit
def getval(self):
return self.value
def __str__(self):
if(self.suit == 'Clubs'):
suitstr = u'\u2663'
elif(self.suit == 'Diamonds'):
suitstr = u'\u2666'
elif(self.suit == 'Hearts'):
suitstr = u'\u2665'
elif(self.suit == 'Spades'):
suitstr = u'\u2660'
if((self.value<11)&(self.value>1)):
valuestr = str(self.value)
elif(self.value == 11):
valuestr = 'J'
elif(self.value == 12):
valuestr = 'Q'
elif(self.value == 13):
valuestr = 'K'
elif((self.value == 1)|(self.value == 14)):
valuestr = 'A'
return(valuestr+suitstr)
class Deck(object):
def __init__(self,DeckCards=[]):
self.DeckCards = DeckCards
def builddeck(self):
suits = ['Hearts','Diamonds','Clubs','Spades']
for suit in suits:
for i in range(13):
self.DeckCards.append(Card(suit,i+1))
def shuffle(self):
for i in range(len(self)):
r = random.randint(0,len(self)-1)
self.DeckCards[i],self.DeckCards[r] = self.DeckCards[r],self.DeckCards[i]
def draw(self):
return self.DeckCards.pop()
def __str__(self):
return str([card.__str__() for card in self.DeckCards])
def __len__(self):
return len(self.DeckCards)
class Player(object):
def __init__(self,Name,PlayerHandcards = [],Balance = 1000):
self.Name = Name
self.Hand = PlayerHandcards
self.Balance = Balance
def deal(self,deck):
self.Hand.append(deck.draw())
def __str__(self):
return 'Name :'+str(self.Name)+'\n'+'Hand: '+str([card.__str__() for card in self.Hand])+'\n'+'Balance: '+str(self.Balance)
deck1 = Deck()
deck1.builddeck()
deck1.shuffle()
Alice = Player('Alice')
Alice.deal(deck1)
print(Alice)
Bob = Player('Bob')
Bob.deal(deck1)
print(Bob)
And after dealing to Bob they both have the same hands. If you have some other suggestions regarding the code, you are welcome to share that as well.
This is a duplicate of “Least Astonishment” and the Mutable Default Argument as indicated by #Mad Physicist. Closing this question for the same.

class method telling me that I used 1 more pos. argument than needed, but i didnt

I get the following error from my code: TypeError: get_manifest() takes 0 positional arguments but 1 was given
I have a class scan, and get_manifest is a function in scan:
import amfy
import requests
import json
from datetime import timedelta, datetime
class Scan:
def __init__(self, realm = 416, cluster = 9):
self.events = list()
self.url = "http://realm{}.c{}0.castle.rykaiju.com/api/locales/en/get_serialized_new".format(realm, cluster)
def get_manifest():
self.request = requests.get(self.url)
if self.request.status_code == 200:
self.manifest = self.request.json() if "json" in self.request.headers["content-type"] else amfy.loads(self.request.content)
else:
self.manifest = {"failed": req.reason}
def get_time(day):
self.today = datetime.now()
self.plusdays = timedelta(days = day)
self.new_date = sel.ftoday + self.plusdays
self.year, self.month, self.day = self.new_date.year, self.new_date.month, self.new_date.day
if len(str(self.day)) == 1:
day = "0{}".format(self.day)
if len(str(self.month)) == 1:
month = "0{}".format(self.month)
self.date = str(self.year) + str(self.month) + str(self.day)
return self.date
def search_events():
for day in range(0, 11):
date = self.get_time(day)
for section in doaManifest:
for key, value in doaManifest[section].items():
if date in key:
event_title = key
try:
event_desc = value['text']
except KeyError:
event_desc = """" /!\ No Event Description /!\""""
my_dict = {'title' : event_title, 'contents' : event_desc}
self.events.append(my_dict)
Then, in another class, which is my app GUI (written with tkinter), I have a button that calls on this class. The button's command is this:
def scan(self):
if self.scan['text'] == 'Scan':
self.scan['text'] = 'Re-Scan'
self.scanned = True
self.scan = Scan()
self.scan.get_manifest()
self.scan.search_events()
I don't feed get_manifest any arguments, so why is it saying I am?
Seems like you forgot adding self to get_manifest(). Redefine get_manifest() like so:
def get_manifest(self):
code...
Instance methods should always accept self as the first argument as it's automatically inserted. You may use #staticmethod if you don't wish to receive it but as you're using self I guess that it's not something you want.
First you need to add the self parameter to all class methods unless you use #staticmethod. So your class should look like this:
class Scan:
def __init__(self, realm = 416, cluster = 9):
# code...
def get_manifest(self):
# code...
def get_time(self, day):
# code...
def search_events(self):
# code...
Furthermore, to use this class within your scan() method you need to initialize and save the class instance to a variable, then call on the get_manifest() method from the saved instance variable.
def scan(self):
# I assume this scan() method is actually a part of a class since 'self' is used
if self.scan['text'] == 'Scan':
self.scan['text'] = 'Re-Scan'
self.scanned = True
#self.scan = Scan() # You are overwriting the definition of 'scan' here...
# So instead do the below
# Initialize and save your Scan() instance to a variable
scan_instance = Scan()
# Now you may use the object's methods
scan_instance.get_manifest()
scan_instance.search_events()
# Here you can now access the object's attributes that were created in get_manifest()
print(scan_instance.manifest)

Resources