Domanda Consiglio programmino

ItsReal

Utente Electrum
30 Dicembre 2014
118
33
9
104
Sera a tutti, ho provato a creare una sorta di programma che funge da inventario per mettere alla prova la mia conoscenza sulle Classi, il codice è questo:

Python:
class Inventario():
    def __init__(self):
        self.inventario = {}
    
    def add_item(self, product_id, product_name, product_quantity):
        quantity = int(product_quantity)
        if product_id in self.inventario:
            product = self.inventario[product_id]
            print('Prodotto già registrato!\n')
            print(f"ID: {product_id}\t\tNome: {product['Nome']}\t\tQuantità: {product['Quantità']})")
            
        else:
            self.inventario[product_id] = {'Nome': product_name, 'Quantità': quantity}
            print('Prodotto aggiunto correttamente!')
    
    def remove_item(self, product_id):
        if product_id in self.inventario:
            product = self.inventario[product_id]
            self.inventario.pop(product_id)
            print(f"{product['Nome']}, ID: {product_id}\t è stato rimosso correttamente!")
        else:
            print('Prodotto non trovato!')
    
    def change_quantity(self, product_id, quantity):
        valor = int(quantity)
        if product_id in self.inventario:
            product = self.inventario[product_id]
            product['Quantità'] = valor
            print(f"ID: {product_id}\t\tNome: {product['Nome']}\t\tQuantità: {product['Quantità']}")
        else:
            print('Prodotto non trovato!')
    
    def add_quantity(self, product_id, quantity):
        valor = int(quantity)
        if product_id in self.inventario:
            product = self.inventario[product_id]
            product['Quantità'] += valor
        else:
            print('Prodotto non trovato!')
    
    def remove_quantity(self, product_id, quantity):
        valor = int(quantity)
        if product_id in self.inventario:
            product = self.inventario[product_id]
            if product['Quantità'] == 0:
                print('Prodotto esaurito!')
            elif product['Quantità'] - valor < 0:
                print('Quantità non sufficente!')
                print(f"Quantità disponibile: {product['Quantità']}")
            else:
                product['Quantità'] -= valor
        else:
            print('Prodotto non trovato!')
    
    def show_all_products(self):
        if len(self.inventario) >= 1:
            for i in self.inventario:
                print(f"ID: {i}\t\tNome: {self.inventario[i]['Nome']}\t\tQuantità: {self.inventario[i]['Quantità']}")
        
        elif len(self.inventario) < 1:
            print('Nessun prodotto registrato!')
                
def start(inventario: Inventario):
    print('\n\t\t*** INVENTARIO ***\n\n')
    print("""
    1) Mostra prodotti registrati
    2) Aggiungi prodotto
    3) Rimuovi prodotto
    4) Modifica quantità prodotto
    5) Aggiungi pezzi
    6) Rimuovi pezzi
    """)
    
    scelta = input('> ')
    
    try:
        if scelta == '1':
            inventario.show_all_products()
        if scelta == '2':
            try:
                codice = int(input('Inserisci codice prodotto: '))
            except ValueError:
                print('Valore non valido!')
                return
            
            try:
                nome = str(input('Inserisci nome prodotto: '))
            except ValueError:
                print('Valore non valido!')
                return
            
            try:
                quantità = int(input('Inserisci la quantità: '))
            except ValueError:
                print('Valore non valido!')
                return
            
            inventario.add_item(codice, nome, quantità)
        
        if scelta == '3':
            try:
                codice = int(input('Inserisci codice prodotto da rimuovere: '))
            except ValueError:
                print('Valore non valido!')
            
            if codice in inventario.inventario:
                inventario.remove_item(codice)
            else:
                print('Prodotto non trovato!')
                return
        
        if scelta == '4':
            try:
                codice = int(input('Inserisci codice prodotto: '))
            except ValueError:
                print('Valore non valido!')
                return
            
            if codice in inventario.inventario:
                try:
                    quantità = int(input('Inserisci la nuova quantità: '))
                except ValueError:
                    print('Valore non valido!')
                    return
            
                inventario.change_quantity(codice, quantità)
            
            else:
                print('Prodotto non trovato!')
                return
        
        if scelta == '5':
            try:
                codice = int(input('Inserisci codice prodotto: '))
            except ValueError:
                print('Valore non valido!')
                return
            
            if codice in inventario.inventario:
                try:
                    quantità = int(input('Inserisci la quantità da aggiungere: '))
                except ValueError:
                    print('Valore non valido!')
                    return
            
                inventario.add_quantity(codice, quantità)
                
            else:
                print('Prodotto non trovato!')
                return
        
        if scelta == '6':
            try:
                codice = int(input('Inserisci codice prodotto: '))
            except ValueError:
                print('Valore non valido!')
                return
            
            if codice in inventario.inventario:
                try:
                    quantità = int(input('Inserisci la quantità da rimuovere: '))
                except ValueError:
                    print('Valore non valido!')
                    return
            
                inventario.remove_quantity(codice, quantità)
                
            else:
                print('Prodotto non trovato!')
                return
    
    except ValueError:
        print('Valore non valido!')
        return
            
    

inventario = Inventario()

while True:
    start(inventario)

Vorrei sapere se c'è un modo per salvare il contenuto dell'inventario su un file di testo così da riaverlo una volta chiuso e riaperto il programma.
Sarei anche grato se mi poteste dare qualche consiglio su come migliorare o velocizzare parti del codice.
Grazie mille come sempre!!
 
Questo potrebbe aiutarti:
Secondo il mio modesto parere non credo sia conveniente salvare i dati inseriti in un file txt, sarebbe poco leggibile, pensa se devi cancellare o modificare la quantità di un prodotto che macello verrebbe fuori.
So che è più complesso a livello di codice però potresti creare un collegamento a un db "inventario" locale in cui salvare i dati nella tabella prodotti.
Avresti un gestione più ordinata lavorando direttamente sulla tabella del db.
 
  • Mi piace
Reazioni: DispatchCode
Questo potrebbe aiutarti:
Secondo il mio modesto parere non credo sia conveniente salvare i dati inseriti in un file txt, sarebbe poco leggibile, pensa se devi cancellare o modificare la quantità di un prodotto che macello verrebbe fuori.
So che è più complesso a livello di codice però potresti creare un collegamento a un db "inventario" locale in cui salvare i dati nella tabella prodotti.
Avresti un gestione più ordinata lavorando direttamente sulla tabella del db.
Va bene, vedo cosa posso fare. Grazie
 
Per cose semplici come questa e dove si vuole distribuire tutta l'applicazione senza dipendenze esterne, si usa SQLite.

 
Ultima modifica:
Sarei anche grato se mi poteste dare qualche consiglio su come migliorare o velocizzare parti del codice.
Stavo riguardando il tuo codice, forse le voci di menù 4, 5 e 6 le potresti raggruppare sotto un unica voce "aggiorna prodotto" o "modifica prodotto" in cui puoi variare la quantità di un prodotto (aggiungere, diminuire la quantità oppure azzerarla del tutto) o modificare anche il nome del prodotto volendo.​
Messaggio unito automaticamente:

In più visto che il codice prodotto è un numero intero che ti fa la funzione di id per me è meglio che lo gestisca il programma e non l'utente.
Mi spiego meglio, quando aggiungi un prodotto devi chiedere all'utente soltanto nome prodotto e quantità, l'id verrà incrementato (auto increment) direttamente dal programma, questo ti tornerà ancora più utile se inserisci i prodotti in una table di un db sql dove l'id sarà la chiave primaria della tabella prodotti.
Poi provando il codice se selezioni la voce "aggiungi prodotto" ed inserisci l'id di un prodotto già esistente, quest'ultimo viene sovrascritto e non credo sia un comportamento che desideri, l'id che si auto-incrementa ti evita di dover mettere un costrutto di controllo dentro la def add_item
 
  • Mi piace
Reazioni: ItsReal
Stavo riguardando il tuo codice, forse le voci di menù 4, 5 e 6 le potresti raggruppare sotto un unica voce "aggiorna prodotto" o "modifica prodotto" in cui puoi variare la quantità di un prodotto (aggiungere, diminuire la quantità oppure azzerarla del tutto) o modificare anche il nome del prodotto volendo.​
Messaggio unito automaticamente:

In più visto che il codice prodotto è un numero intero che ti fa la funzione di id per me è meglio che lo gestisca il programma e non l'utente.
Mi spiego meglio, quando aggiungi un prodotto devi chiedere all'utente soltanto nome prodotto e quantità, l'id verrà incrementato (auto increment) direttamente dal programma, questo ti tornerà ancora più utile se inserisci i prodotti in una table di un db sql dove l'id sarà la chiave primaria della tabella prodotti.
Poi provando il codice se selezioni la voce "aggiungi prodotto" ed inserisci l'id di un prodotto già esistente, quest'ultimo viene sovrascritto e non credo sia un comportamento che desideri, l'id che si auto-incrementa ti evita di dover mettere un costrutto di controllo dentro la def add_item
In realtà io volevo far si che l'ID sia proprio un codice del prodotto e non un valore incrementale. Es 000150078
Magari posso gestire l'ID (01, 02, 03..) in automatico come hai detto te e aggiungere un argomento "Codice prodotto" per il reale codice del prodotto.
 
  • Mi piace
Reazioni: TheWorm91
In realtà io volevo far si che l'ID sia proprio un codice del prodotto e non un valore incrementale. Es 000150078
Magari posso gestire l'ID (01, 02, 03..) in automatico come hai detto te e aggiungere un argomento "Codice prodotto" per il reale codice del prodotto.
Si ho capito cosa intendi però ti complichi la vita, se vuoi potresti generare il codice in automatico tipo con le prime tre lettere del nome prodotto...
 
Si ho capito cosa intendi però ti complichi la vita, se vuoi potresti generare il codice in automatico tipo con le prime tre lettere del nome prodotto...
Se io volessi implementare il codice e gestire il tutto con un database MySQL mi servirebbe solo creare il menù di scelta con python, senza la classe Inventario?
 
Una cosa che dovresti fare è separare la parte della logica del programma da quello che è invece il menù, la stampa a schermo etc.

Così puoi anche scegliere di creare una GUI, e dovrai occuparti solo di richiamare le funzioni (la logica). Come stai facendo ora non potresti.

Anche per la parte che interroga il db, fai attenzione. Usa un framework, e non mischiare le query direttamente in quelle funzioni.
 
  • Mi piace
Reazioni: TheWorm91
Se io volessi implementare il codice e gestire il tutto con un database MySQL mi servirebbe solo creare il menù di scelta con python, senza la classe Inventario?
Una cosa che dovresti fare è separare la parte della logica del programma da quello che è invece il menù, la stampa a schermo etc.
Avrei pensato al volo a questa soluzione un po' grezza:
file inventario.py con i metodi per gestire il db:
Python:
import sqlite3

class DbMgt():
    '''classe DbMgt per la gestione del db magazzino'''
    def __init__(self):
        self.con = sqlite3.connect("magazzino.db")
        self.cur = self.con.cursor()
        
    def close(self):
        self.con.close()
    def create_table_prodotti(self):
        self.cur.execute('''CREATE TABLE IF NOT EXISTS prodotti
                         (id INTEGER PRIMARY KEY,
                          nome varchar(20) NOT NULL,
                          quantita int NOT NULL)''')
    def show_prodotti(self):
        self.cur.execute('''SELECT * from prodotti''')
        result=self.cur.fetchall()
        for row in result:
            print('\nid:',row[0],'\nnome:',row[1],'\nquantita:',row[2])
    def add_prodotto(self,nome,quantita):
        self.nome = nome
        self.quantita = quantita
        self.cur.execute('''INSERT INTO prodotti (nome, quantita)
                            VALUES (?,?)
                            ''', (self.nome, self.quantita))
        self.con.commit()
    def update_table_prodotti(self,condition):
        self.condition = condition
        nome=str(input("inserisci nuovo nome prodotto: "))
        quantita=int(input("aggiorna quantità: "))
        self.cur.execute('''UPDATE prodotti
                            SET nome =?, quantita =?
                            WHERE id =?''',(nome, quantita, self.condition))
        self.con.commit()

Poi nel programma principale ho fatto l'import di inventario.py e ho creato un oggetto inventario= inventario.DbMgt()
per richiamare i metodi che servono in base all'opzione di menù scelta dall'utente.
file main che richiama i metodi:
Python:
import sys
import inventario.py
inventario = inventario.DbMgt()
def start(inventario):
    inventario.create_table_prodotti()
    print('\n\t\t****** MAGAZZINO ******\n\n')
    print("""
    1) Visualizza tutti i prodotti
    2) Aggiungi prodotto
    3) Modifica prodotto
    0) Esci
    """)
    scelta = input('> ')
    try:
        if scelta == '1':
            inventario.show_prodotti()
        if scelta == '2':
            try:
                nome = str(input('Inserisci il nome prodotto: '))
            except ValueError:
                print('Valore non valido!')
                return
            try:
                quantita = int(input('Inserisci la quantità: '))
                except ValueError:
                    print('Valore non valido!')
                    return
            inventario.add_prodotto(nome,quantita)
        if scelta == '3':
            inventario.show_prodotti()
            print("Seleziona l'id del prodotto di cui vuoi modificare i dati oppure esci: ")
            try:
                condition = int(input('> '))
            except ValueError:
                print('Valore non valido')
                return
            inventario.update_table_prodotti(condition)
        if scelta == '0':
            sys.exit()
    except ValueError:
        print('Exit')
        return
while True:
    start(inventario)
Messaggio unito automaticamente:

nello scrivere il file principale con il menù mi sono accorto di un "errore" ovvero che ho messo le istruzioni di input nel codice principale invece che dentro la def add_prodotto in inventario.py come ho fatto per l'opzione di update
 
Ci può stare secondo me.
Perché non usi il pattern matching invece di if-elif?

Chiedo, magari c'è qualche ragione in particolare (non conosco python). Mi sembra meno efficiente, considerando il nr di confronti inutili che viene sempre fatto (sottigliezze, sia chiaro).
 
  • Mi piace
Reazioni: ItsReal
cosa intendi? come si vede nel codice che ho postato ho messo le query dentro le def per la gestione del db, non va bene?

Ho riguardato bene il codice, ho visto che è l'interfaccia che ti espone. Questo è ok.

Ciò che farei io è astrarre ulteriormente, quindi la show prodotto richiama una funzione diversa, che è quella che gestisce il db (un CRUD, e l'inizializzazione della connessione al DB).

Questo perché se avessi bisogno di usare un'altra volta un'INSERT, piuttosto di un UPDATE, non devi riscrivere la query con la sola differenza.

Questo è ciò che farei io. Ovviamente se si sta imparando, come nel caso dell'utente, andrebbe benissimo come hai fatto tu.

Btw, normalmente i framework che ho usato poi ti danno la possibilità di usare una raw query, ma espongono altre funzioni, tipo find() inserti() etc dove poi passi i parametri. Però ragiono anche in termini di OOP, dove hai una classe che rispecchia la struttura del db.
 
  • Mi piace
Reazioni: ItsReal
Presumendo tu stia studiando python e OOP ti riporto qualche suggerimento sulla tua prima soluzione:
  • Gia' che sei agli inizi perdi la brutta abitudine di:
    • scrivere codice meta' in italiano e meta' in inglese.
    • non tipizzare il codice o tipizzarlo parzialmente.
    • non commentare il codice
    • creare funzioni lunghe chilometriche non single purpose
    • non utilizzare costanti per accedere alle chiavi di un dizionario
  • Se stai lavorando con le classi perche' non definire anche una classe menu e InventoryItem? In generale OOP e' molto piu' che un class ed un __init__
  • Cerca di separare i feedback all'utente dalla logica delle classi ma piu' in generale. In questo caso la tua funzione start ( il menu ) e' il layer di contatto tra inventario e utente. Rendi evidente questa separazione.

Al contrario di quanto proposto fino ad ora, ovvero utilizzare sqlite, ti propongo una soluzione diversa basata su JSON che ti evita di dover lavorare con query ed sql che trovo fuori contesto per imparare OOP con l'esempio da te riportato.
Utilizzare il caso dell'inventario con sql lo trovo piu' funzionale per apprendere SQL che OOP di python.
Caso diverso e molto didattico, e' quello di implementare un semplice ORM rudimentale.

Passando alla soluzione:

Una semplice rappresentazione del prodotto in inventario:
Python:
from typing import Dict
from inventory_exceptions import InvalidInventoryItemQuantity


class InventoryItem:

    def __init__(self, code: int, name: str, quantity: int):
        self.code: int = code
        self.name: str = name
        self.quantity: int = quantity

    def update_quantity(self, quantity: int):
        self.quantity += quantity
        if self.quantity < 0:
            self.quantity -= quantity
            raise InvalidInventoryItemQuantity

    def to_dict(self) -> Dict:
        return {"code": self.code, "name": self.name, "quantity": self.quantity}

    def __repr__(self) -> str:
        return f"ID: {self.code}\t\tNome: {self.name}\t\tQuantità: {self.quantity}"

Il nostro inventario
Python:
import json
from typing import Dict, Optional

from inventory_item import InventoryItem
from inventory_exceptions import EmptyInventory, InventoryItemNotFound, InventoryItemDuplicatedEntry


class InventoryJsonEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, InventoryItem):
            return obj.to_dict()
        return super().default(obj)


class Inventory:

    INVENTORY_FILE = "inventory.json"

    def __init__(self):
        self._inventory: Dict[int, InventoryItem] = {}
        self._load_inventory_from_file()

    def _load_inventory_from_file(self):
        try:
            with open(self.INVENTORY_FILE, "r") as inventory_file:
                json_inventory = json.load(inventory_file)
        except FileNotFoundError:
            print(f"Impossibile caricare l'inventario dal file: {self.INVENTORY_FILE} ")
            return

        self._inventory = {}
        item_value: Dict
        for item_key, item_value in json_inventory.items():
            self._inventory[int(item_key)] = InventoryItem(*item_value.values())

    def save_inventory(self):
        with open(self.INVENTORY_FILE, "w") as inventory_file:
            json.dump(self._inventory, inventory_file, cls=InventoryJsonEncoder)

    def item_exists(self, item_id: int) -> bool:
        return item_id in self._inventory

    def get_item(self, item_id: int) -> Optional[InventoryItem]:
        return self._inventory.get(item_id)

    def add_item(self, item_id: int, item_name: str, item_quantity: int):
        if self.item_exists(item_id):
            raise InventoryItemDuplicatedEntry

        new_item = InventoryItem(item_id, item_name, item_quantity)
        self._inventory[item_id] = new_item

    def remove_item(self, item_id: int) -> InventoryItem:
        try:
            return self._inventory.pop(item_id)
        except KeyError:
            raise InventoryItemNotFound

    def update_item_quantity(self, item_id: int, quantity: int):
        try:
            self._inventory[item_id].update_quantity(quantity)
        except KeyError:
            raise InventoryItemNotFound

    def show_inventory_items(self):
        if not self._inventory:
            raise EmptyInventory

        for item in self._inventory.values():
            print(item)

Ci sono semplice custom Exceptions che vengono sollevate sia dall inventario che dagli oggetti in inventario
Python:
class InvalidInventoryItemQuantity(Exception):
    pass


class InventoryItemNotFound(Exception):
    pass


class InventoryItemDuplicatedEntry(Exception):
    pass


class EmptyInventory(Exception):
    pass

Ed infine il menu
Python:
import sys
from typing import Dict
from inventory import Inventory
from inventory_exceptions import (
    EmptyInventory, InventoryItemNotFound, InvalidInventoryItemQuantity, InventoryItemDuplicatedEntry
)


class Menu:

    def __init__(self):
        self.inventory: Inventory = Inventory()
        self.menu_options: Dict = {
            "0": self.option_exit_menu,
            "1": self.option_show_inventory_items,
            "2": self.option_add_item_to_inventory,
            "3": self.option_update_item_in_inventory,
            "4": self.option_delete_item_from_inventory
        }

    def option_exit_menu(self):
        self.inventory.save_inventory()
        sys.exit()

    def option_show_inventory_items(self):
        try:
            self.inventory.show_inventory_items()
        except EmptyInventory:
            print("Inventario vuoto!")

    def option_add_item_to_inventory(self):
        item_id, item_name, item_quantity = (
            int(input('Inserisci codice prodotto: ')),
            str(input('Inserisci nome prodotto: ')),
            int(input('Inserisci la quantità: '))
        )
        if item_id < 0:
            raise ValueError

        try:
            self.inventory.add_item(item_id, item_name, item_quantity)
        except InventoryItemDuplicatedEntry:
            print('Prodotto già registrato!\n')
            print(self.inventory.get_item(item_id))

    def option_delete_item_from_inventory(self):
        item_id = int(input('Inserisci codice prodotto: '))
        try:
            removed_item = self.inventory.remove_item(item_id)
            print(f"({removed_item.code}, {removed_item.name}) è stato rimosso correttamente!")
        except InventoryItemNotFound:
            print('Prodotto non trovato!')

    def option_update_item_in_inventory(self):
        item_id, item_quantity = (int(input('Inserisci codice prodotto: ')), int(input('Inserisci la quantità: ')))
        try:
            self.inventory.update_item_quantity(item_id, item_quantity)
        except InventoryItemNotFound:
            print('Prodotto non trovato!')
        except InvalidInventoryItemQuantity:
            print('Quantità non sufficente!')
            print(f"Quantità disponibile: {self.inventory.get_item(item_id).quantity}")

    def start_menu_loop(self):
        while True:
            print("\n\t\t****** MAGAZZINO ******\n\n")
            print("""
                1) Visualizza tutti i prodotti
                2) Aggiungi prodotto
                3) Modifica prodotto
                4) Elimina prodotto
                0) Salva ed Esci
                """)
            selection: str = input('> ')
            try:
                callable_menu_function = self.menu_options[selection]
                try:
                    callable_menu_function()
                except ValueError:
                    print('Valore non valido!')
            except KeyError:
                print("Selezione Invalida")


if __name__ == "__main__":
    menu = Menu()
    menu.start_menu_loop()

Note sulla soluzione.
Soluzione compatibile con python >= 3.8
Mi scuso per non aver inserito commenti ma typing e naming dovrebbero essere sufficienti a comprendere.
Se utilizzi una versione di python > 3.8 puoi utilizzare le primitive per il Dict
E' stata aggiunta una piccola classe per fare l'encoding degli oggetti InventoryItem
JSON non supporta int come tipi per le chiavi, quindi la rilettura deve castare il giusto tipo.
L'inventario funziona sulla base del dict in memoria e sincronizza i dati con il file e dal file all'avvio e alla chiusura dell'inventario.
 
  • Mi piace
Reazioni: ItsReal
Presumendo tu stia studiando python e OOP ti riporto qualche suggerimento sulla tua prima soluzione:
  • Gia' che sei agli inizi perdi la brutta abitudine di:
    • scrivere codice meta' in italiano e meta' in inglese.
    • non tipizzare il codice o tipizzarlo parzialmente.
    • non commentare il codice
    • creare funzioni lunghe chilometriche non single purpose
    • non utilizzare costanti per accedere alle chiavi di un dizionario
  • Se stai lavorando con le classi perche' non definire anche una classe menu e InventoryItem? In generale OOP e' molto piu' che un class ed un __init__
  • Cerca di separare i feedback all'utente dalla logica delle classi ma piu' in generale. In questo caso la tua funzione start ( il menu ) e' il layer di contatto tra inventario e utente. Rendi evidente questa separazione.

Al contrario di quanto proposto fino ad ora, ovvero utilizzare sqlite, ti propongo una soluzione diversa basata su JSON che ti evita di dover lavorare con query ed sql che trovo fuori contesto per imparare OOP con l'esempio da te riportato.
Utilizzare il caso dell'inventario con sql lo trovo piu' funzionale per apprendere SQL che OOP di python.
Caso diverso e molto didattico, e' quello di implementare un semplice ORM rudimentale.

Passando alla soluzione:

Una semplice rappresentazione del prodotto in inventario:
Python:
from typing import Dict
from inventory_exceptions import InvalidInventoryItemQuantity


class InventoryItem:

    def __init__(self, code: int, name: str, quantity: int):
        self.code: int = code
        self.name: str = name
        self.quantity: int = quantity

    def update_quantity(self, quantity: int):
        self.quantity += quantity
        if self.quantity < 0:
            self.quantity -= quantity
            raise InvalidInventoryItemQuantity

    def to_dict(self) -> Dict:
        return {"code": self.code, "name": self.name, "quantity": self.quantity}

    def __repr__(self) -> str:
        return f"ID: {self.code}\t\tNome: {self.name}\t\tQuantità: {self.quantity}"

Il nostro inventario
Python:
import json
from typing import Dict, Optional

from inventory_item import InventoryItem
from inventory_exceptions import EmptyInventory, InventoryItemNotFound, InventoryItemDuplicatedEntry


class InventoryJsonEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, InventoryItem):
            return obj.to_dict()
        return super().default(obj)


class Inventory:

    INVENTORY_FILE = "inventory.json"

    def __init__(self):
        self._inventory: Dict[int, InventoryItem] = {}
        self._load_inventory_from_file()

    def _load_inventory_from_file(self):
        try:
            with open(self.INVENTORY_FILE, "r") as inventory_file:
                json_inventory = json.load(inventory_file)
        except FileNotFoundError:
            print(f"Impossibile caricare l'inventario dal file: {self.INVENTORY_FILE} ")
            return

        self._inventory = {}
        item_value: Dict
        for item_key, item_value in json_inventory.items():
            self._inventory[int(item_key)] = InventoryItem(*item_value.values())

    def save_inventory(self):
        with open(self.INVENTORY_FILE, "w") as inventory_file:
            json.dump(self._inventory, inventory_file, cls=InventoryJsonEncoder)

    def item_exists(self, item_id: int) -> bool:
        return item_id in self._inventory

    def get_item(self, item_id: int) -> Optional[InventoryItem]:
        return self._inventory.get(item_id)

    def add_item(self, item_id: int, item_name: str, item_quantity: int):
        if self.item_exists(item_id):
            raise InventoryItemDuplicatedEntry

        new_item = InventoryItem(item_id, item_name, item_quantity)
        self._inventory[item_id] = new_item

    def remove_item(self, item_id: int) -> InventoryItem:
        try:
            return self._inventory.pop(item_id)
        except KeyError:
            raise InventoryItemNotFound

    def update_item_quantity(self, item_id: int, quantity: int):
        try:
            self._inventory[item_id].update_quantity(quantity)
        except KeyError:
            raise InventoryItemNotFound

    def show_inventory_items(self):
        if not self._inventory:
            raise EmptyInventory

        for item in self._inventory.values():
            print(item)

Ci sono semplice custom Exceptions che vengono sollevate sia dall inventario che dagli oggetti in inventario
Python:
class InvalidInventoryItemQuantity(Exception):
    pass


class InventoryItemNotFound(Exception):
    pass


class InventoryItemDuplicatedEntry(Exception):
    pass


class EmptyInventory(Exception):
    pass

Ed infine il menu
Python:
import sys
from typing import Dict
from inventory import Inventory
from inventory_exceptions import (
    EmptyInventory, InventoryItemNotFound, InvalidInventoryItemQuantity, InventoryItemDuplicatedEntry
)


class Menu:

    def __init__(self):
        self.inventory: Inventory = Inventory()
        self.menu_options: Dict = {
            "0": self.option_exit_menu,
            "1": self.option_show_inventory_items,
            "2": self.option_add_item_to_inventory,
            "3": self.option_update_item_in_inventory,
            "4": self.option_delete_item_from_inventory
        }

    def option_exit_menu(self):
        self.inventory.save_inventory()
        sys.exit()

    def option_show_inventory_items(self):
        try:
            self.inventory.show_inventory_items()
        except EmptyInventory:
            print("Inventario vuoto!")

    def option_add_item_to_inventory(self):
        item_id, item_name, item_quantity = (
            int(input('Inserisci codice prodotto: ')),
            str(input('Inserisci nome prodotto: ')),
            int(input('Inserisci la quantità: '))
        )
        if item_id < 0:
            raise ValueError

        try:
            self.inventory.add_item(item_id, item_name, item_quantity)
        except InventoryItemDuplicatedEntry:
            print('Prodotto già registrato!\n')
            print(self.inventory.get_item(item_id))

    def option_delete_item_from_inventory(self):
        item_id = int(input('Inserisci codice prodotto: '))
        try:
            removed_item = self.inventory.remove_item(item_id)
            print(f"({removed_item.code}, {removed_item.name}) è stato rimosso correttamente!")
        except InventoryItemNotFound:
            print('Prodotto non trovato!')

    def option_update_item_in_inventory(self):
        item_id, item_quantity = (int(input('Inserisci codice prodotto: ')), int(input('Inserisci la quantità: ')))
        try:
            self.inventory.update_item_quantity(item_id, item_quantity)
        except InventoryItemNotFound:
            print('Prodotto non trovato!')
        except InvalidInventoryItemQuantity:
            print('Quantità non sufficente!')
            print(f"Quantità disponibile: {self.inventory.get_item(item_id).quantity}")

    def start_menu_loop(self):
        while True:
            print("\n\t\t****** MAGAZZINO ******\n\n")
            print("""
                1) Visualizza tutti i prodotti
                2) Aggiungi prodotto
                3) Modifica prodotto
                4) Elimina prodotto
                0) Salva ed Esci
                """)
            selection: str = input('> ')
            try:
                callable_menu_function = self.menu_options[selection]
                try:
                    callable_menu_function()
                except ValueError:
                    print('Valore non valido!')
            except KeyError:
                print("Selezione Invalida")


if __name__ == "__main__":
    menu = Menu()
    menu.start_menu_loop()

Note sulla soluzione.
Soluzione compatibile con python >= 3.8
Mi scuso per non aver inserito commenti ma typing e naming dovrebbero essere sufficienti a comprendere.
Se utilizzi una versione di python > 3.8 puoi utilizzare le primitive per il Dict
E' stata aggiunta una piccola classe per fare l'encoding degli oggetti InventoryItem
JSON non supporta int come tipi per le chiavi, quindi la rilettura deve castare il giusto tipo.
L'inventario funziona sulla base del dict in memoria e sincronizza i dati con il file e dal file all'avvio e alla chiusura dell'inventario.
Parto con l'idea di seguire i tuoi consigli e ringrazio @DispatchCode e @TheWorm91 per il supporto, ovviamente con l'intenzione di arrivare anche ad imparare MySQL.
Per quanto riguarda il codice da te proposto cercherò ovviamente di capirlo passo per passo, dato l'elevato uso di funzioni e moduli da me sconosciuti.
Non capisco cosa intendi per non tipizzare il codice e le OOP sono più che un class e __init__
Grazie mille
 
Parto con l'idea di seguire i tuoi consigli e ringrazio @DispatchCode e @TheWorm91 per il supporto, ovviamente con l'intenzione di arrivare anche ad imparare MySQL.
Per quanto riguarda il codice da te proposto cercherò ovviamente di capirlo passo per passo, dato l'elevato uso di funzioni e moduli da me sconosciuti.
Non capisco cosa intendi per non tipizzare il codice e le OOP sono più che un class e __init__
Grazie mille

OOP sta per Object Oriented Programming ed e' un paradigma di programmazione basato su oggetti ( le tue classi per semplificare).
Tra i principali aspetti della OOP ci sono:
  • Encapsulation
  • Inheritance
  • Polymorphism
Dire pertanto che si sta lavorando con classi implementando una singola classe con il suo __init__, e' un po' come dire di poter gareggiare in formula uno avendo tenuto in mano un volante di una panda spenta. ( so che sei agli inizi)

La tipizzazione del codice riguarda principalmente input e output delle funzioni nonche' gli attributi delle classi.

Per il resto del codice postato, quale sarebbe quello sconosciuto? Sono tutte funzioni built-in di python ad eccezione di json che comunque fa parte dello standard package.
 
  • Mi piace
Reazioni: TheWorm91
OOP sta per Object Oriented Programming ed e' un paradigma di programmazione basato su oggetti ( le tue classi per semplificare).
Tra i principali aspetti della OOP ci sono:
  • Encapsulation
  • Inheritance
  • Polymorphism
Dire pertanto che si sta lavorando con classi implementando una singola classe con il suo __init__, e' un po' come dire di poter gareggiare in formula uno avendo tenuto in mano un volante di una panda spenta. ( so che sei agli inizi)

La tipizzazione del codice riguarda principalmente input e output delle funzioni nonche' gli attributi delle classi.

Per il resto del codice postato, quale sarebbe quello sconosciuto? Sono tutte funzioni built-in di python ad eccezione di json che comunque fa parte dello standard package.
Io penso che il mio problema risiedi nella mia ignoranza sulle Classi.
Ho notato che utilizzi spesso questa sintassi:
Python:
def to_dict(self) -> Dict:
La freccia è solo un modo per classificare un determinato tipo di valore?
 
OOP sta per Object Oriented Programming ed e' un paradigma di programmazione basato su oggetti ( le tue classi per semplificare).
Tra i principali aspetti della OOP ci sono:
  • Encapsulation
  • Inheritance
  • Polymorphism
Io penso che il mio problema risiedi nella mia ignoranza sulle Classi.
Forse per imparare a lavorare su classi, polimorfismo, ereditarietà e incapsulamento potrebbe essere un buon esercizio scrivere un programma che calcola area e perimetro di una figura piana, partendo da una classe figura_piana si possono creare le varie sottoclassi cerchio, rettangolo ecc...
 
Io penso che il mio problema risiedi nella mia ignoranza sulle Classi.
Ho notato che utilizzi spesso questa sintassi:
Python:
def to_dict(self) -> Dict:
La freccia è solo un modo per classificare un determinato tipo di valore?
la freccia e' il modo di annotare (definire il tipo) di ritorno di una funzione.
Nel caso specifico indico che sto ritornando un dizionario
 
  • Mi piace
Reazioni: ItsReal