Programowanie obiektowe, tworzenie instancji klasy przy pomocy metody tej klasy,

0

Witajcie,

Stawiam pierwsze kroki w pythonie, programowanie obiektowe spędza mi sen z powiek, może ktoś podpowie mi co jest nie tak z moim kodem i gdzie mogę go poprawić?

Moim zadaniem było utworzenie klasy ShoppingCart z atrybutem "products", który będzie przechowywał słownik z dodanymi produktami. W zadaniu nie zostało wyjaśnione jakie wartości mają przechowywać klucze (kluczem jest id produktu). Ponieważ metoda print_receipt ma podawać nazwę, cenę oraz, ilość i łączną cenę produktu oraz łączną kwotę za wszystkie produkty, uznałem, że będzie przechowywać dane o produkcie bez jego opisu. Metoda print_receipt ma także korzystać z metody get_total_sum w celu zliczenia łącznej ceny produktu.
Oto moje pytania:

  • Czy istnieje możliwość jednoczesnego dodania nowego produktu (korzystając z metody add_product) do koszyka (słownik) products oraz stworzenia instancji produktu np wykorzystując tą metodę?
    Wydaje mi się, że nie mogę tego zrobić, ponieważ metoda odnosi się do instancji, a wywołując ją przy pomocy klasy (ShoppingCart.add_product()) - wymaga ode mnie dodatkowego argumentu (self)
    W tej chwili robię to następująco:
p2 = ShoppingCart('p2', 'p2', 10, 4) #wynik total_sum 40
p3 = ShoppingCart('p3', '3', 3, 40) #wynik total_sum 120
p2.add_product(p2)
p3.add_product(p3)

Wiem, że powyższy kod zadziała, ale dziwnie to dla mnie wygląda, że wywołuję metodę z poziomu instancji, a i tak muszę przekazać jej dane

  • Nie potrafię wywołać metody print_receipt dla całej klasy (dla wszystkich instancji atrybutu products) bez odwołania się do konkretnego obiektu. Gdy odwołuję się bezpośrednio ShoppingCart.print_receipt() wyrzuca TypeError, missing 1 required positional argument: 'self' (rozumiem ten błąd)
    Z kolei gdy odwołam się do metody poprzez instancję to, (w przypadku dodania wcześniej większej ilości produktów do koszyka) podlicza mi przy każdym produkcie ilości i ceny z danej instancji, łączna suma za wszystkie produkty też jest wtedy nieprawidłowa. Tutaj także rozumiem, że metoda self.get_total_sum() pobiera za każdym wykonaniem pętli dane tej samej instancji. Nie mam pomysłu jak zrobić to w sposób prawidłowy. Jak prawidłowo, z poziomu klasy, przy pomocy metody get_total_sum zrobić poprawne obliczenia.
print(p2.print_receipt()) # daje wynik 80
print(p3.print_receipt()) # daje wynik 120

Pradoopdobnie cały mój print można zrobić w bardziej pythonowy sposób, proszę o jakieś wskazówki.

class Product:
    next_id = 1

    def __init__(self, name, description, price, quantity):
        self.name = name
        self.description = description
        self.price = price
        self.quantity = quantity
        self.id = Product.next_id
        Product.next_id += 1

    def get_total_sum(self):
        return self.price * self.quantity

    @property
    def get_id(self):
        return self.id


class ShoppingCart(Product):

    products= {}

     def add_product(self, new_product):
         if new_product.get_id not in ShoppingCart.products:
             ShoppingCart.products[new_product.get_id] = [self.name, self.price, self.quantity]

    def remove_product(self, product_id):
        if product_id not in ShoppingCart.products:
            pass
        elif ShoppingCart.products[product_id]:
            ShoppingCart.products.pop(product_id)
            
    def change_product_quantity(self, new_quantity):
        ShoppingCart.products[self.id][-1] = new_quantity

    def print_receipt(self):
        total_amount = 0
        for i in ShoppingCart.products:
            print( f'{ShoppingCart.products[i][0]} - {ShoppingCart.products[i][1]}, '
                   f'{ShoppingCart.products[i][2]}, {self.get_total_sum()}')
                   total_amount += self.get_total_sum()
        return f'Total amount = {total_amount}'
1

o_O Ale przecież p2.add_product(p2) powoduje dodanie shoppingCart o nazwie p2 do twojego koszyka zakupów p2.To nie ma żadnego sensu. Powinieneś tam robić jakieś p2.add_product(Product(cośtam cośtam)) albo

p = Product(cośtam cośtam)
cart = ShoppingCart()
cart.add_product(p)

Reszta kodu też jest źle bo zrobiłeś products w Shopping cart jako zmienna statyczną klasy a nie pole instancji!

class ShoppingCart(Product):
    def __init__(self):
        self.products= {}

teraz to dopiero miałoby jakiś sens. Przecież koszyków można zrobić dużo i do jednego chcesz dać jabłka a do drugiego pomarańcze!
Radzę zaorać ten kod do 0 bo do niczego sie nie nadaje. Przeczytać ze zrozumieniem jakiś kurs a potem spróbować jeszcze raz.

2

W modelowaniu obiektowym dziedziczenie oznacza relację "jest". Na przykład jak masz klasę "Animal" to może z niej dziedziczyć klasa "Cat", ponieważ stwierdzenie "kot jest zwierzęciem" ma sens. Kompozycja (zawieranie składowej) modeluję relację "zawiera". Klasycznym przykładem jest samolot (klasa Plane) i skrzydło (klasa Wing) - samolot posiada 2 składowe skrzydeł. Skrzydło nie dziedziczy po samolocie, bo stwierdzenie "skrzydło jest samolotem" nie ma sensu.

U ciebie zaś mamy klasę koszyk na zakupy, która dziedziczy po produkcie, a zatem sama jest produktem. To jakbyś szedł do sklepu, wziął koszyk, włożył do niego dwa inne koszyki na zakupy i poszedł je kupić. Spodziewam się jednak, że sklep raczej nie pozwala sprzedawać własnego wyposażenia, jedynie własne produkty. Ergo powinno być coś takiego:

class Product:
    pass

class ShoppingCart:
    def add_product(self, product):
        # ...

cart = ShoppingCart()
cart.add_product(Product())
cart.add_product(Product())
# ...
0

Dziękuję bardzo za podpowiedzi. Poprawiłem kod i mam jeszcze jedno pytanie, do którego odnosiłem się wcześniej.
Utworzyłem dodatkowy atrybut instacji ShoppingCart o nazwie self.product, który ma przechowywać utworzony przy dodawaniu koszyka obiekt.
Utworzyłem go, ponieważ nie widzę innego sposobu, aby skorzystać z metody get_total_sum klasy Product, ponieważ metoda print_receipt nie przyjmuje żadnych argumentów, a powinna wykonać obliczenia na podstawie utworzonych w koszyku produktów.
Czy istnieje inny sposób na przekazanie do funkcji get_total_sum, który obiekt/produkt ma wziąć do obliczeń, tak żebym nie musiał tworzyć tego dodatkowego atrybutu product? Teoretycznie moje zadanie mówiło o utworzeniu tylko atrybutu products (słownika z produktami).

class Product:
    next_id = 1

    def __init__(self, name, description, price, quantity):
        self.name = name
        self.description = description
        self.price = price
        self.quantity = quantity
        self.id = Product.next_id
        Product.next_id += 1

    def get_total_sum(self):
        return self.price * self.quantity

    @property
    def get_id(self):
        return self.id

class ShoppingCart(Product):

    def __init__(self):
        self.products= {}
        self.product = None

    def add_product(self, new_product):
        id = new_product.get_id
        self.product = new_product
        if id not in self.products:
            self.products[id] = [new_product.name, new_product.description, new_product.price,
                                 new_product.quantity]

    def print_receipt(self):
        total_amount = 0
        for i in self.products:
            print( f'{self.products[i][0]} - {self.products[i][2]}, '
                   f'{self.products[i][3]}, {self.product.get_total_sum()}')
            total_amount += self.product.get_total_sum()
        return f'Total amount = {total_amount}'

@Shalom kurs (słono płatny) mam za sobą, z różnych przyczyn miałem roczną przerwę po kursie gdzie nic nie kodowałem i teraz staram się sobie wszystko przypomnieć. Niestety większość tutoriali, kursów (także mój) i podpowiedzi jakie znajduje w internecie to są banały. Nawet materiały jakie mi pozostały po kursie omawiają tylko najprostsze przykłady. Niestety nie radzę sobie jeszcze z zadawaniem odpowiednich zapytań do google po angielsku, więc postanowiłem posiłkować się na polskim forum. Regularnie przeglądam stackoverflow, ale język techniczny momentami stanowi dla mnie sporą barierę. To co napisaliście powyżej wbrew pozorom bardzo mi pomogło, bo wcześniej chyba nawet nie zastanowiłem się co tak naprawdę próbuję stworzyć,a raczej co tworzy moja aplikacja (odnośnie wielu koszyków i tego, że koszyk nie jest produktem). Myślę, że pytań z biegiem czasu będę miał więcej, mam nadzieję, że mój kod będzie miał wtedy więcej sensu :)

0

A jesteś pewien co do tego polecenia? Bo dla mnie to ten get total ma liczyć coś w stylu ilość*cena dla elementów koszyka i w ogóle nie ma nic wspólnego z tym co tu próbujesz zrobić. W ogóle cały ten design wygląda źle. Nikt normalny nie napisałaby tak sklepu.

W rzeczywistości miałbyś jakąś klasę która zwraca informacje o każdym produkcie (bo produkty byłyby zdefiniowane wcześniej np w jakiejś bazie danych, ale tutaj możesz je zdefiniować w kodzie jako dict productid->informacje).
W koszyku miałbyś dict który trzyma productid->ilość
Liczenie sumy koszyka sumowaloby cena_jednostki*ilość dla wszystkich produktów w koszyku.
Wpisanie rachunku dla każdego elementu w koszyku wpisywałby informacje o produkcie (nazwa, cena jednostki etc) oraz informacje o liczbie takich produktów w koszyku.

Nawet jak odpuścimy sobie teraz tego InfoProvidera i założymy że trzymasz w koszyku obiekty Product te wszystkimi informacjami, to nadal to jest proste jak budowa cepa i nie wymaga takich fikołków:

import uuid
from collections import defaultdict


class Product:
    def __init__(self, name, description, price):
        self.name = name
        self.description = description
        self.price = price
        self.id = uuid.uuid4()

    def __str__(self):
        return f'{self.id}:{self.name}, {self.description}, {self.price}'


class ShoppingCart:

    def __init__(self):
        self.products = defaultdict(int)

    def add_product(self, product, quantity):
        self.products[product] += quantity

    def remove_product(self, product, quantity):
        self.products[product] -= quantity
        if self.products[product] <= 0:
            del self.products[product]

    def get_total_value(self):
        return sum([product.price * quantity for product, quantity in self.products.items()])

    def get_receipt(self):
        result = 'Cart receipt:\n'
        for product, quantity in self.products.items():
            result += f'{product} x {quantity} = {product.price * quantity}\n'
        result += f'Total amount = {self.get_total_value()}'
        return result


if __name__ == '__main__':
    p1 = Product("test1", "some description", 123)
    p2 = Product("test2", "some description2", 345)

    cart1 = ShoppingCart()
    cart1.add_product(p1, 3)
    print(cart1.get_receipt())
    cart1.add_product(p2, 4)
    print(cart1.get_receipt())

    cart2 = ShoppingCart()
    cart2.add_product(p1, 1)
    print(cart2.get_receipt())
    cart2.add_product(p1, 1)
    print(cart2.get_receipt())
    cart2.remove_product(p1, 2)
    print(cart2.get_receipt())

Zobacz co się dzieje:

  1. Nie mam jakiegoś quantity w klasie Product, bo przecież ilość cebuli jaką kupuje jest zdefiniowana przez mój koszyk, a nie przez produkt cebula. Cebula to cebula, ja wezmę jedną, ty weźmiesz sobie 2 do koszyka.
  2. Nie mam jakiegoś self.product w klasie ShoppingCart bo i jaki to miało sens? o_O
  3. Mój koszyk nie dziedziczy z produktu, bo nie planuje sprzedawać koszyków, zresztą taka rekurencyjna struktura wymagałaby trochę kombinacji (bo co jak ktoś włoży do koszyka koszyk z zakupami? musisz jako "cenę" tego zagnieżdżonego koszyka podać cenę wszystkich zakupów w nim)
  4. Używam normalnej pętli po elementach slownika a nie jakichś cudów na kiju
0

Na sam produkt poswiecilbym z dwie klasy -> Cos w stylu klasy bazowej np. ProductDetails ktora byla by dziedziczona przez zwykly Product, gdzie tak na prawde tylko inicjalizujesz instancje a cala magia jest w klasie bazowej. Poprawilo by to czytelnosc i zauwazylbys wiecej zaleznosci :) Potem tak jak mowia przedmowcy -> Dodajesz do koszyku instancje klasy Produkt i tam juz w srodku masz metody odnosnie cen, informacni itp.

0

@Shalom:
Dziękuję za uwagi.
Co do polecenia - jest to zadanie z kursu, na który uczęszczałem w 2019.
Sposób rozwiązania tego jaki przedstawiłeś jest zdecydowanie bardziej sensowny, niestety nie wypełnia wymogów zadania (czyt. metoda get_total w klasie produkt). Jako osoba początkująca staram się wypełniać zadania tak jak mam podane, bo ufam, że ten kto pisał zadanie wiedział co robi. O tym, że zadania na kursie były z czapy (nie pisząc bardziej dosadnie) sygnalizowałem na samym kursie, zresztą miałem później rozmowę na ten temat z jednym z wykładowców, który podzielił moje zdanie. Niestety materiały do pracy mam jakie mam, więc i takie pojawiło się moje pytanie.
Dodam, że to zadanie nie przewiduje jeszcze obsługi baz danych i w takiej konwencji trzeba je rozwiązać.
Z tego co piszesz wniosek płynie dla mnie taki, że na przyszłość nie powinienem trzymać się sztywno przedstawionej treści tylko skupić się nad budową jak najefektywniejszego rozwiązania o ile efekt końcowy będzie taki jak w poleceniu. Wcześniej faktycznie nie zastanowiłem się choćby nad tym, że quantity nie ma racji bytu w klasie Product, a teraz strasznie kłuje w oczy.

@ledi12 będę wdzięczny jeśli pokażesz mi przykład takiej budowy klas PoductDetails i Product.

0
class ProductDetails:
    def __init__(self, name, quantity, type):
        self._name = name
        self._quantity = quantity
        self._type = type

    @property
    def name(self):
        return self._name
    
    @property
    def quantity(self):
        return self._quantity
    
    @property
    def type(self):
        return self._type

class Product(ProductDetails):
    def __init__(self, name, quantity, type):
        self._name = name
        self._quantity = quantity
        self._type = type
        ProductDetails.__init__(self, name, quantity, type)
    
    def eat_product(self):
        self._quantity -= 1

p1 = Product("milk", 20, "dairy")
print(p1.quantity)
p1.eat_product()
print(p1.quantity)

<< 20
<< 19

1 użytkowników online, w tym zalogowanych: 0, gości: 1