Reputation: 287
I have defined a class named ShoppingItem
.
class ShoppingItem:
def __init__(self, code, description, price=1.0, quantity=1):
self.code = int(code)
self.description = str(description)
self.price = float(price)
self.quantity = int(quantity)
def __str__(self):
if self.quantity == 0:
return ("Code = {}, {} - Out of Stock".format(self.code, self.description))
else:
return ("Code = {}, {}, ${:.2f}, quantity = {}".format(self.code, self.description, self.price, self.quantity))
def __repr__(self):
return ("ShoppingItem({}, '{}', {}, {})".format(self.code,self.description, self.price, self.quantity ))
def set_price(self, new_price):
if new_price > 0:
self.price = new_price
Now I am required to define another class Shopping
. I need to implement the load_data(self)
method which should invoke the read_file()
method to get a list of strings, convert the list of strings into a list of ShoppingItem
objects and assign the list into the self.items_list
variable. The read_file()
method reads in the contents from the self.filename
file. If the file cannot be read properly, then the self.items_list
remains unchanged (i.e. an empty list).
class Shopping:
def __init__(self, filename = 'stocks.txt'):
self.filename = filename
Shopping.items_list = []
def read_file(self):
try:
input_file = open(self.filename, 'r')
contents = input_file.read()
content_list = contents.split('\n')
except FileNotFoundError:
print ("ERROR: The file '{}' does not exist.".format(self.filename))
else:
input_file.close()
return content_list
def load_data(self):
self.items_list = ShoppingItem(self.read_file())
#here is where I am having problem writing the code. Can someone guide me to complete this
Can someone please tell me how I can achieve what is in the expected output. How do I convert the list of strings into a list of ShoppingItem
objects
Test:
shop = Shopping('stocks.txt')
shop.load_data()
print(len(shop.items_list))
print(type(shop.items_list[0]))
print(type(shop.items_list[0].code))
print(type(shop.items_list[0].description))
print(type(shop.items_list[0].price))
print(type(shop.items_list[0].quantity))
Expected Output:
10
<class '__main__.ShoppingItem'>
<class 'int'>
<class 'str'>
<class 'float'>
<class 'int'>
Output Received:
***Error***
Traceback (most recent call last):
File "__tester__.python3", line 66, in <module>
shop.load_data()
File "__tester__.python3", line 33, in load_data
self.items_list = ShoppingItem(self.read_file())
TypeError: __init__() missing 1 required positional argument: 'description'
**SAMPLE TEXT FILE CONTENTS:
11,Coca Cola Soft Drink 500ml,4,2
12,L & P Soft Drink Lemon & Paeroa 500ml,4,9
13,V Blue Drink can 500mL,3.5,8
14,V Vitalise Energy Drink 500ml,3.5,5
15,Pump Water NZ Spring 750ml,2.5,9
16,Twix Chocolate Bar 50g,2.5,12
17,Nestle Kit Kat Chocolate Bar 4 Finger, 2.4,15
18,Snickers Chocolate Bar 50g,2,11
19,Cadbury Chocolate Bar Crunchie 50g, 2,13
20,Cadbury Picnic Chocolate Bar 46g,2,15
Upvotes: 1
Views: 75
Reputation: 11662
Firstly, I'd suggest abstracting the loading of the ShoppingItem
objects as a class method in the ShoppingItem
class itself. This is a bit easier as you don't need to define another class which returns a list of ShoppingItem
objects.
I'd also suggest looking into dataclasses, which I personally like for this use case, as it provides a nice layer of abstraction around Python classes, which also helps to improve on code quality a little.
For the price
field, I'd also suggest using properties in Python, as the nice thing is that the setter method is invoked whenever you use the obj.price = x
syntax, for example.
I feel like these changes actually aligns well with the "Zen of Python":
Simple is better than complex.
# needed to support `str | int` syntax in Python 3.7+
from __future__ import annotations
from dataclasses import dataclass, field
from pathlib import Path
from pprint import pprint
@dataclass
class ShoppingItem:
code: int
description: str
price: float = 1.0
quantity: int = 1
# needed for better IDE support
_price: float = field(init=False, repr=False)
def __post_init__(self):
self.code = int(self.code)
self.description = str(self.description)
self.price = self.price
self.quantity = int(self.quantity)
def __str__(self):
if not self.quantity:
return "Code = {}, {} - Out of Stock".format(self.code, self.description)
return (
"Code = {}, {}, ${:.2f}, quantity = {}".format(self.code, self.description, self.price, self.quantity))
# noinspection PyRedeclaration
@property
def price(self) -> float:
return self._price
@price.setter
def price(self, new_price: str | float):
new_price = float(new_price)
if new_price > 0:
self._price = new_price
@classmethod
def from_file(cls, filename: str) -> list[ShoppingItem]:
"""Create list of ShoppingItems from a comma-separated file."""
lines = Path(filename).read_text().splitlines()
shopping_items = [cls(*line.split(',')) for line in lines]
return shopping_items
my_items = ShoppingItem.from_file('my_file.txt')
print('Count of Shopping Items:', len(my_items))
pprint(my_items)
With recent changes and much needed improvements to the builtin pprint
module added in Python 3.10, it now correctly pretty-prints dataclass instances as desired:
Count of Shopping Items: 10
[ShoppingItem(code=11,
description='Coca Cola Soft Drink 500ml',
price=4.0,
quantity=2),
ShoppingItem(code=12,
description='L & P Soft Drink Lemon & Paeroa 500ml',
price=4.0,
quantity=9),
...]
Upvotes: 0
Reputation: 227
You can use a csv.DictReader to get the contents of the file. https://docs.python.org/3/library/csv.html
Change the read_file method to something like
with open(self.filename, 'r') as input_file:
reader = csv.DictReader(input_file)
for row in reader:
self.items_list.append(ShoppingItem(**row))
assuming the args on ShoppingItem equal the header row of the csv file otherwise you will need to tweak a bit
Upvotes: 0
Reputation: 413
Your list_of_str
will contain all the data in a comma-separated-value format, and the data of each item will be separated by a new line.
You can loop through each item in the list_of_str
then extract the values using .split(',')
method. This will give you code, desc, price, quant
(in this particular sequence)
Then you need to push the object of ShoppingItem
in the item_list
for a particular item. You can do so by creating a new object ShoppingItem(code=code, description=desc, price=price, quantity=quant)
and then appending it to the item_list
.
so the code will look like this:
def load_data(self):
list_of_strs = self.read_file()
for item in list_of_strs:
code, desc, price, quant = item.split(',')
self.items_list.append(ShoppingItem(code=code, description=desc, price=price, quantity=quant))
Upvotes: 1