El problema está en la línea
return('{}'.format(catalogo))
pero viene de antes, implementas mal la clase.
Declaras un atributo de clase que luego no usas
class Catalogue():
catalogo=[]
^^^^^^^^^^
para empezar no debe ser un atributo de clase sino de instancia, dado que cada objeto catálogo tendrá sus productos y no todos los catálogos los mismos productos (atributo de clase).
Además de ello, jamás llegas a usarlo realmente, ésto se debe a que no lo referencias desde los métodos con instancia.catalogo
(self.catalogo
) o Catalogue.catalogo
(cls.catalogo
).
Mírate ésta pregunta para entender la diferencia entre ambos tipos de atributos:
En los métodos de instancia usas realmente la variable global catalogo
class Catalogue():
def remove_product(self,p):
catalogo.remove(p)
#^^^^^^^
# esto
def __str__(self):
return('{}'.format(catalogo))
#^^^^^^^^
# y ésto
catalogo=Catalogue()
#^^^^^^^^
# son ésto
ésta es la razón de que termines con una recursión infinita:
print(producto)
llama a producto.__str__
.
- Cuándo se evalúa
return('{}'.format(catalogo))
str.format
llama a producto.__str__
de nuevo.
- Y así hasta el infinito y más allá, bueno hasta que llegas a 1000 llamadas recursivas que es el límite por defecto que tiene Python para proteger al propio intérprete de un desbordamiento de pila.
En el método add_product
realmente no haces nada
Sí haces realmente, pero no hay efectos fuera del mismo. Aunque catalogo
es una variable global como se ha explicado, al hacer catalogo = []
(reasignación) se crea automáticamente una variable local al método que solapa la global, luego añades el producto a la lista, pero como buena variable local, en cuanto el método retorna el GC manda a la lista a mejor vida. Esa lista jamás podrá ser accedida desde fuera del método.
En definitiva, debes usar un atributo de instancia y no de clase, el código podría quedar así, con anotaciones de tipo incluidas:
from typing import List, Union
class CatalogueException(Exception):
pass
class StockException(Exception):
pass
class Product:
def __init__(self, code: str, name: str, price: Union[float, int]):
self._code: str = code
self._name: str = name
self._price: Union[float, int] = price
def __hash__(self) -> int:
return hash(self._code)
def __str__(self) -> str:
return f'Code: {self._code}\nName: {self._name}\nPrice: {self._price}'
class Catalogue:
def __init__(self) -> None:
self._products: List[Product] = []
def add_product(self, product: Product) -> None:
self._products.append(product)
def remove_product(self, product: Product) -> None:
self._products.remove(product)
def __str__(self) -> str:
prods = "\n\n".join(str(prod) for prod in self._products)
return f"{'-' * 34} CATÁLOGO {'-' * 34}\n{prods}\n{'-' * 78}"
if __name__ == "__main__":
catalogo = Catalogue()
new_spanner = Product("S78", "Adjustable spanner", 35.99)
new_hammer = Product("A45", 'Bush hammer', 15.5)
catalogo.add_product(new_spanner)
catalogo.add_product(new_hammer)
print(catalogo)
Es recomendable que no uses catalogo
como nombre del atributo de la clase Catalogo
, es confuso. products
es un mejor nombre para la lista.
El uso de _nombre
es una convención para marcar atributos/métodos "privados". Es correcto su uso, pero no tiene sentido en el nombre de los argumentos en este caso.