import abc
import math


class BasePizza(object):
"""
This particular way of implementing abstract method
has a drawback. If you write a class that inherits
from Pizza and forget to implement get_radius, the error
will only be raised when you'll try to use that method
"""
__metaclass__ = abc.ABCMeta
default_ingredients = ['cheese']

@classmethod
@abc.abstractmethod
def get_ingredients(cls):
"""Returns the ingredient list."""
return cls.default_ingredients

@abc.abstractmethod
def get_radius(self):
"""Method that should do something."""


class Calzone(BasePizza):
def get_ingredients(self, with_egg=False):
mushroom = ["mushroom"] if with_egg else None
return super(Calzone, self).get_ingredients() + mushroom


class DietPizza(BasePizza):
def get_ingredients(self):
""" you can have code in your abstract methods and call it via super():"""
return ['egg'] + super(DietPizza, self).get_ingredients()


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

@staticmethod
def compute_area(radius):
"""code that belongs to a class, but that doesn't use the object itself at all. """
return math.pi * (radius ** 2)

@classmethod
def compute_volume(cls, height, radius):
"""methods that are not bound to an object, but to… a class!"""
return height * cls.compute_area(radius)

def get_volume(self):
"""instance method"""
return self.compute_volume(self.height, self.radius)


class DietPizza(BasePizza):
def get_ingredients(self):
return ['egg'] + super(DietPizza, self).get_ingredients()


if __name__ == "__main__":
p = Pizza(3, 4)
print(p.get_volume())

p1=DietPizza()
print(p1.get_ingredients())

p2=Calzone()
print(p2.get_ingredients(True))