"""
Métodos de instancia
   El primer método en MyClass, llamado método, es un método de instancia 
   regular. Ese es el tipo de método básico y sencillo que utilizará la 
   mayor parte del tiempo. Puede ver que el método toma un parámetro, 
   self, que apunta a una instancia de MyClass cuando se llama al método 
   (pero, por supuesto, los métodos de instancia pueden aceptar más de 
   un parámetro).

   A través del parámetro self, los métodos de instancia pueden acceder 
   libremente a atributos y otros métodos en el mismo objeto. Esto les 
   da mucho poder cuando se trata de modificar el estado de un objeto.

   No solo pueden modificar el estado del objeto, los métodos de instancia 
   también pueden acceder a la clase misma a través del atributo self 
   .__ class__. Esto significa que los métodos de instancia también 
   pueden modificar el estado de la clase.

Métodos de clase
   Comparemos eso con el segundo método, MyClass.classmethod. Marqué 
   este método con un decorador @classmethod para marcarlo como un método 
   de clase.

   En lugar de aceptar un parámetro self, los métodos de clase toman un 
   parámetro cls que apunta a la clase, y no a la instancia del objeto, 
   cuando se llama al método.

   Debido a que el método de clase solo tiene acceso a este argumento cls, 
   no puede modificar el estado de la instancia del objeto. Eso requeriría 
   acceso a uno mismo. Sin embargo, los métodos de clase aún pueden 
   modificar el estado de la clase que se aplica a todas las instancias 
   de la clase.

Métodos estáticos
   El tercer método, MyClass.staticmethod se marcó con un decorador 
   @staticmethod para marcarlo como un método estático.

   Este tipo de método no toma un parámetro self ni cls (pero, por supuesto,
   es libre de aceptar un número arbitrario de otros parámetros).

   Por lo tanto, un método estático no puede modificar el estado del 
   objeto ni el estado de la clase. Los métodos estáticos están restringidos 
   en cuanto a los datos a los que pueden acceder, y son principalmente 
   una forma de asignar un espacio de nombres a sus métodos.

"""


class MyClass:
    def method(self):
        return "instance method called", self

    @classmethod
    def classmethod(cls):
        return "class method called", cls

    @staticmethod
    def staticmethod():
        return "static method called"


obj = MyClass()
print(obj.method())
print(MyClass.method(obj))
print(obj.classmethod())
print(obj.staticmethod())


print(MyClass.classmethod())

print(MyClass.staticmethod())

print(MyClass.method("dd"))


import math


class Pizza:
    def __init__(self, radius, ingredients):
        self.radius = radius
        self.ingredients = ingredients

    def __repr__(self):
        return f"Pizza({self.radius!r}, " f"{self.ingredients!r})"

    @classmethod
    def margherita(cls):
        return cls(3, ["mozzarella", "tomatoes"])

    @classmethod
    def prosciutto(cls):
        return cls(4, ["mozzarella", "tomatoes", "ham"])

    def area(self):
        return self.circle_area(self.radius)

    @staticmethod
    def circle_area(r):
        return r ** 2 * math.pi


Pizza(4, ["cheese", "tomatoes"])
Pizza(2, ["mozzarella", "tomatoes"])
Pizza(3, ["mozzarella", "tomatoes", "ham", "mushrooms"])
Pizza(4, ["mozzarella"] * 4)


print(Pizza.margherita())

print(Pizza.prosciutto())


p = Pizza(4, ["mozzarella", "tomatoes"])
print(p)

print(p.area())

print(Pizza.circle_area(4))
