# Calculate various shapes Area and Perimeter

I am writing Python program which calculates area and perimeter of various 2d shapes. I am trying to learn about OOP and this is the first project which I wrote from scratch.

I'm wondering how to let user now what attributes he should use in order to get the correct result from particular class? For ex:

gives me an atttribute error because the correct name should be radius not radi. I would like to handle this error and print the correct attribute name.

from math import pi

"""
Exercise (Shape Area and Perimeter Classes):

Create an abstract class called Shape and then inherit from it other shapes like
diamond, rectangle, cirlce, etc. Then have each class override the area and
perimeter functionality to handle each shape type.

I wrote 20 classes to handle various shapes
Geometry figures taken from: http://www.aplustopper.com/mensuration-rs-aggarwal-class-7-maths-solutions-exercise-20c/
"""

"""
init method takes all arguments in key=value format
str method returns all given attributes in column
"""
class Shape:
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)

def __str__(self):
all_info_str = ""
for attribute, value in self.__dict__.items():
if value > 0:
all_info_str += attribute + ": " + "%.2f" %(value) + "\n"
return self.__class__.__name__ + "\n" + all_info_str

"""
Rectangle class attributes must be:
lenght=x, width=x
"""
class Rectangle(Shape):
def __init__(self, **kwargs):
super().__init__(**kwargs)

def count_area(self):
self.area = self.lenght * self.width

def count_perimeter(self):
self.perimeter = 2 * (self.lenght + self.width)

"""
Square class attributes must be:
lenght=x
"""
class Square(Shape):
def __init__(self, **kwargs):
super().__init__(**kwargs)

def count_area(self):
self.area = self.lenght ** 2

def count_perimeter(self):
self.perimeter = 4 * self.lenght

"""
Triangle class attributes must be:
base=x, height=x, leg_a=x, leg_b=x
"""
class Triangle(Shape):
def __init__(self, **kwargs):
super().__init__(**kwargs)

def count_area(self):
self.area = 1/2 * self.base * self.height

def count_perimeter(self):
self.perimeter = self.leg_a + self.base + self.leg_b

"""
RightTriangle class attributes must be:
base=x, height=x, hypotenuse=x
"""
class RightTriangle(Shape):
def __init__(self, **kwargs):
super().__init__(**kwargs)

def count_area(self):
self.area = self.base + self.height + self.hypotenuse

def count_perimeter(self):
self.perimeter = 1/2 * self.base * self.height

"""
EquilateralTriangle class attributes must be:
leg_a=x
"""
class EquilateralTriangle(Shape):
def __init__(self, **kwargs):
super().__init__(**kwargs)

def count_area(self):
self.area = math.sqrt(3) / 4 * self.leg_a ** 2

def count_perimeter(self):
self.perimeter = 3 * self.leg_a

"""
IsoscelecRightTriangle class attributes must be:
leg_a=x, hypotenuse=x
"""
class IsoscelecRightTriangle(Shape):
def __init__(self, **kwargs):
super().__init__(**kwargs)

def count_area(self):
self.area = 1/2 * self.leg_a ** 2

def count_perimeter(self):
self.perimeter = 2 * self.leg_a + self.hypotenuse

"""
Parallelogram class attributes must be:
lenght=x, height=x, width=x
"""
class Parallelogram(Shape):
def __init__(self, **kwargs):
super().__init__(**kwargs)

def count_area(self):
self.area = self.lenght * self.height

def count_perimeter(self):
self.perimeter = 2 * (self.lenght + self.width)

"""
Rhombus class attributes must be:
hypotenuse_1=x, hypotenuse_2=x, lenght=x
"""
class Rhombus(Shape):
def __init__(self, **kwargs):
super().__init__(**kwargs)

def count_area(self):
self.area = 1/2 * self.hypotenuse_1 * self.hypotenuse_2

def count_perimeter(self):
self.perimeter = 4 * self.lenght

"""
Trapezium class attributes must be:
height=x, top=x, base=x, leg_a=x, leg_b=x
"""
class Trapezium(Shape):
def __init__(self, **kwargs):
super().__init__(**kwargs)

def count_area(self):
self.area = 1/2 * self.height * (self.top + self.base)

def count_perimeter(self):
self.perimeter = self.leg_a + self.leg_b + self.top + self.base

"""
Circle class attributes must be:
"""
class Circle(Shape):
def __init__(self, **kwargs):
super().__init__(**kwargs)

def count_area(self):
self.area = pi * self.radius ** 2

def count_perimeter(self):

"""
Ring class attributes must be:
"""
class Ring(Shape):
def __init__(self, **kwargs):
super().__init__(**kwargs)

def count_area(self):

def count_perimeter(self):
self.perimeter = (pi * 2 * self.radius1) - (pi * 2 * self.radius2)

"""
CircleSector attributes must be:
"""
class CircleSector(Shape):
def __init__(self, **kwargs):
super().__init__(**kwargs)

def count_area(self):
self.area = self.angle / 360 * pi * self.radius ** 2

def count_perimeter(self):
self.perimeter = (self.angle / 360 * 2 * pi * self.radius) +\

if __name__ == '__main__':
R = Rectangle(lenght=2, width=5)
R.count_area()
R.count_perimeter()
print(R)

C.count_area()
C.count_perimeter()
print(C)


First, the docstring goes inside of the class definition:

class Circle(Shape):
"""
Circle class attributes must be:
"""
...


This allows you to e.g. call help(Circle) in an interactive shell and get back this string. This is documented in PEP257.

To answer your question how the user is to know what the parameters are, just make them explicit in the constructors:

class Circle(Shape):
self.perimeter = 2 * pi * radius


This way it is actually useful to override the __init__ method. If all you do is call the parents constructor (without any change of arguments) you could just leave it out.

Note that I also moved the calculation of perimeter and area into the constructor. This way the class is usable right after construction and you don't need to call some arcane calculate this property function.

I also fixed your calculation for the perimeter and area of the circle.

Normally I would probably make them an actual property:

class Circle(Shape):

@property
def area(self):

@property
def perimeter(self):
return 2 * pi * self.radius


But this has the disadvantage that you cannot discover it from the __dict__ anymore. But then again, these are two fixed names, so you could hardcode them in Shape.__str__:

class Shape:
def __init__(self, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)

def __str__(self):
all_info = [self.__class__.__name__]
for attribute, value in self.__dict__.items():
if value > 0:
all_info.append(f"{attribute}: {value:.2f}")
all_info.append(f"{area}: {self.area:.2f}")
all_info.append(f"{perimeter}: {self.perimeter:.2f}")
return "\n".join(all_info)


Note that I removed your string addition, because it is very costly in Python (strings are immutable, so adding two strings involves allocating a new string of the correct size and copying the content of the two strings there), especially if you do many of them, as you do here. Instead I used a list of strings, which I str.join in the end.

I also used the new f-string to make the formatting a lot easier to read.

A few basic things -

1. Length is misspelled "lenght"
2. Right triangle's area and perimeter are switched
3. I'm not sure the perimeter of the circle is the correct formula, but I'm no mathematician
4. All of these are named as count - wouldn't they be sum?