yowhatsup123
yowhatsup123

Reputation: 287

OOP Python converting list of strings into a list of ShoppingItem objects and assign the list into the self.items_list variable

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

Answers (3)

Wizard.Ritvik
Wizard.Ritvik

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

Kevin Smeeks
Kevin Smeeks

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

WinterSoldier
WinterSoldier

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

Related Questions