"Documentation" can mean a few things in the Python community. At the simplest level, documentation can just be the docstrings included with your modules, classes, and functions. The next level up would probably be external user documentation files, probably written in Markdown or reStructuredText, using a utility such as Sphinx to convert the original files into other formats such as HTML or PDF. These documentation files are the ones anyone who wants to use your library would reference. Finally, if you want other contributors to have specific information about assisting with development, you can include external developer documentation files, also written in some markup language and converted to various formats with sphinx
.
Even within the single format of docstrings, there are multiple ways to format the contents of the docstrings, such as Epydoc and Google's Python Style Guide. We'll be using the reStructuredText within our docstrings in order to allow Sphinx to automatically include them within the generated documentation files. We'll also be including some fairly superfluous docstrings, one for each multi-file module, module, class, and function, so that we can see exactly what Sphinx will do with them.
polygons/__init__.py
becomes:
"""Provides utilities for calculating polygon areas.
"""
from . import polygon
from . import regularpolygon
__all__ = ['polygon', 'regularpolygon']
polygons/polygon/__init__.py
:
"""Provides utilities for working with (irregular) polygons.
"""
from .polygon import Polygon
polygons/polygon/polygon.py
:
class Polygon:
"""A class representing any polygon as a list of sides and a list of angles.
"""
names = {
3: 'triangle',
4: 'quadrilateral',
5: 'pentagon',
6: 'hexagon',
7: 'heptagon',
8: 'octagon',
9: 'nonagon',
10: 'decagon',
}
def __init__(self, sides:int, lengths:list, angles:list):
"""Initialize the Polygon class.
:param sides: The number of sides the polygon has
:param lengths: A list of lengths for each side
:param angles: A list of angles, angle i is between lengths i and i + 1
:raises ValueError: if arguments are invalid for any reason
"""
self.sides = sides
self.lengths = lengths
self.angles = angles
self.confirm_attributes()
@property
def perimeter(self) -> float:
"""Return the perimiter of the polygon.
"""
return sum(self.lengths)
@property
def name(self) -> str:
"""Return the name of the polygon.
"""
return self.names[self.sides]
def confirm_attributes(self):
"""Confirm attributes are within bounds restrictions.
"""
if (not isinstance(self.sides, int)) and (not self.sides.is_integer()):
raise ValueError('sides must be an integer')
if type(self.lengths) is not list:
raise ValueError('lengths must be a list')
if len(self.lengths) != self.sides:
raise ValueError('lengths count must equal number of sides')
if not all(isinstance(length, (int, float)) for length in self.lengths):
raise ValueError('lengths must be numeric')
if not all(length > 0 for length in self.lengths):
raise ValueError('lengths must be positive')
if type(self.angles) is not list:
raise ValueError('angles must be a list')
if len(self.angles) != self.sides:
raise ValueError('angles count must equal number of sides')
if not all(isinstance(angle, (int, float)) for angle in self.angles):
raise ValueError('angles must be numeric')
if sum(self.angles) != ((self.sides - 2) * 180):
raise ValueError('angles must sum correctly')
if not all(angle > 0 for angle in self.angles):
raise ValueError('angles must be positive')
polygons/regularpolygon/__init__.py
:
"""Provides utilities for calculating regular polygon areas.
"""
from .regularpolygon import RegularPolygon
from .square import Square
from .equilateraltriangle import EquilateralTriangle
polygons/regularpolygon/regularpolygon.py
:
import math
from ..polygon.polygon import Polygon
class RegularPolygon(Polygon):
"""A class representing any regular polygon.
"""
def __init__(self, sides:int, length:float):
"""Initialize the RegularPolygon class.
:param sides: The number of sides the regular polygon has
:param length: The length of each side
:raises ValueError: if sides or length are invalid values
"""
self.length = length
lengths = [length] * sides
angles = [self.angle(sides)] * sides
super().__init__(sides, lengths, angles)
@property
def perimeter(self) -> float:
"""Return the perimiter of the regular polygon.
"""
return self.sides * self.length
@property
def apothem(self) -> float:
"""Return the apothem of the regular polygon.
"""
tan = math.tan(math.pi / self.sides)
return self.length / (2 * tan)
@property
def area(self) -> float:
"""Return the area of the regular polygon.
"""
if self.sides == 4:
return self.length ** 2
else:
return (self.apothem * self.perimeter) / 2
@classmethod
def angle(self, sides:int) -> float:
"""Return the calculated angle for a regular polygon with the given sides.
:param sides: The number of sides of the regular polygon
:raise ValueError: if sides < 3 or not an int
"""
if sides < 3:
raise ValueError('cannot calculate angles of polygon with less than 3 sides')
elif type(sides) != int:
raise ValueError('sides must be an int')
angle_sum = 180 * (sides - 2)
return angle_sum / sides
polygons/regularpolygon/square.py
:
from .regularpolygon import RegularPolygon
class Square(RegularPolygon):
"""A class to represent a square.
"""
def __init__(self, length:float):
"""Initialize the Square class.
"""
super().__init__(4, length)
@property
def area(self):
"""Return the area of the square.
"""
return self.length ** 2
@property
def name(self):
"""Override `Polygon.name` function.
"""
return 'square'
polygons/regularpolygon/equilateraltriangle.py
:
import math
from .regularpolygon import RegularPolygon
class EquilateralTriangle(RegularPolygon):
"""A class to represent an equilateral triangle.
"""
def __init__(self, length:float):
"""Initialzie the EquilateralTriangle class.
"""
super().__init__(3, length)
@property
def area(self) -> float:
"""Return the area of the equilateral triangle.
"""
return (self.length ** 2) * math.sqrt(3) / 4
@property
def name(self) -> str:
"""Override Polygon.area function.
"""
return 'equilateral triangle'