7
\$\begingroup\$

I have a task to learn how to write clear and understandable documentation of written code in python. Please, can you give me a feedback to attached code below? I need to know if the code and comments explains the functionality of code properly.

# math operations library
import numpy as np

# for testing of classes
import unittest

# ========================== NODE ==================================== #
#Class representing geometric node in 3D
class Node:
    def __init__(self, x=0, y=0, z=0):
        self.X = x
        self.Y = y
        self.Z = z

# =========================== TRIANGLE =============================== #
# class representing triangle ABC
class Triangle:
    def __init__(self, a, b, c):
        self.A = a
        self.B = b
        self.C = c

    # returns area of triangle in 2D
    def Area(self):
        return (
                np.absolute(
                    self.A.X * (self.B.Y - self.C.Y)
                    + self.B.X * (self.C.Y - self.A.Y)
                    + self.C.X * (self.A.Y - self.B.Y)
                    ) / 2.0
                )

# unittests for triangle object
class TriangleTest(unittest.TestCase):
    def test_area(self):
        # triangle node
        A = Node(0,0)
        B = Node(1,0)
        C = Node(0,1)

        T = Triangle(A,B,C)
        self.assertEquals(T.Area(), 0.5)

# ============================ Transformation ======================== #

"""
class representing transformation from reference triangle
with: A = [0,0],
      B = [1,0],
      C = [0,1].
"""
class Transformation:
    def __init__(self, triangle):
        # Base node - A node of triangle
        self.A = triangle.A

        # Matrix B(2x2) representing transformation operator
        self.B11 = triangle.B.X - triangle.A.X
        self.B12 = triangle.C.X - triangle.A.X
        self.B21 = triangle.B.Y - triangle.A.Y
        self.B22 = triangle.C.Y - triangle.A.Y

        # determinant of transformation = B matrix (2x2)
        self.Jacobian = (
                (self.B11 * self.B22) - (self.B21 * self.B12)
                )

        # Inverse of transformation operator = invB matrix (2x2)
        self.invB11 = 1 / self.Jacobian * (self.B22)
        self.invB12 = - 1 / self.Jacobian * (self.B12)
        self.invB21 = - 1 / self.Jacobian * (self.B21)
        self.invB22 = 1 / self.Jacobian * (self.B11)

    def TransformNode(self, node):
        """
        This function returns coordinates of node, that is transformed
        from referance coordinates system to general coordinate system
        of given 2D triangle.

        Args:

        * node - geometrical node with coordinates in reference system

        Returns:

        * node with transformed coordinates into general coordinates
        system of given 2D triangle
        """

        return Node(
                self.A.X + (self.B11 * node.X) + (self.B12 * node.Y),
                self.A.Y + (self.B21 * node.X) + (self.B22 * node.Y)
                )

    def InvTransformNode(self, node):
        """
        This function returns coordinates of node, thah is transformed
        from general coordinate system of given 2D triangle to refere-
        nce coordinate system.

        Args:

        * node - geometrical node with coordinates in general system

        Returns:

        * node with transformed coordinates to reference coordinate
        system
        """

        return Node(
                self.invB11 * (node.X - self.A.X)
                + self.invB12 * (node.Y - self.A.Y),
                self.invB21 * (node.X - self.A.X)
                + self.invB22 * (node.Y - self.A.Y)
                )

# unittests for transformation object
class TransformationTest(unittest.TestCase):
     def test_NodeTransformation(self):
         """
         Checking if node D is transformed to reference system and back
         correctly
         """

         # triangle nodes
         A = Node(1,1)
         B = Node(3,1)
         C = Node(4,2)

         # transformed node
         D = Node(2,3)

         T = Triangle(A,B,C)
         Trans = Transformation(T)

         # retransformed node D
         resD = Trans.TransformNode(Trans.InvTransformNode(D))

         # compare result coordinates
         self.assertEquals(resD.X, D.X)
         self.assertEquals(resD.Y, D.Y)


# ============================== testing ============================= #
# Test Triangle object
suite1 = unittest.TestLoader().loadTestsFromTestCase(TriangleTest)
unittest.TextTestRunner(verbosity=3).run(suite1)

# Test Transformation object
suite2 = unittest.TestLoader().loadTestsFromTestCase(TransformationTest)
unittest.TextTestRunner(verbosity=3).run(suite2)
\$\endgroup\$
  • 1
    \$\begingroup\$ (Welcome to CR!) Do you know docstrings? That is one thing python got right: documentation amidst essential parts of code. This still gets out of sync, but separating it from the code would require extra/criminal energy. \$\endgroup\$ – greybeard Dec 3 '17 at 23:03
  • \$\begingroup\$ Many thanks, it seem to be interesting, I will check it. \$\endgroup\$ – VaPo Dec 3 '17 at 23:05
  • 1
    \$\begingroup\$ @VaPo not a large enough suggestion to be it's own answer, but use a namedtuple for the Node type - as in, Node = namedtuple('Node', ['X', 'Y', 'Z']). It's shorter, clearer, and will provide you with some useful debugging stuff as well. \$\endgroup\$ – naiveai Dec 4 '17 at 3:31
  • \$\begingroup\$ Even when it works for your example numbers, you should not check if a float is 0.5, but if abs(x-0.5) < eps, e.g. with eps=1e-14. Else there will be cases where floating point arithmetic leads to small rounding errors even when you think your numbers are even. I.e. you have 0.1+0.2 == 0.30000000000000004 in python. \$\endgroup\$ – allo Dec 4 '17 at 14:13
  • \$\begingroup\$ Thank you for your time and answers, I really appreciate it. \$\endgroup\$ – VaPo Dec 9 '17 at 11:49
15
\$\begingroup\$

Here are a few of the high- and low-level ideas:

  • too many comments. Remember, comments tend to age and outdate, they need to be maintained as the code changes. And, if they are over-used, they hurt readability, not improve it. See more at Coding Without Comments
  • convert some of the comments preceding the functions and methods to proper documentation strings
  • there are some PEP8 naming violations - like variable and function names that start with an upper case letter
  • see if you can separate code from the tests into different files/modules
  • if performance or/and memory usage is a concern, you may use __slots__ for attribute definitions
  • there is also this awesome attrs library that may help with some class-attrs related boilerplate code
\$\endgroup\$
  • 2
    \$\begingroup\$ +1 on this. Instead of those huge distracting banner comments, just put every class in it's own file. \$\endgroup\$ – Qwertie Dec 4 '17 at 2:57
3
\$\begingroup\$

The code is very readable and the comments are nice. Just a few remarks on some comments:

  • Why don't Node and Triangle have docstring comments?
  • Importing libraries doesn't need commentary.
\$\endgroup\$
  • \$\begingroup\$ Having Node and Triangle docstrings seems a bit meaningless since they're such obvious classes - you can digest the code in 3 seconds flat. \$\endgroup\$ – naiveai Dec 4 '17 at 3:29
  • \$\begingroup\$ You could make it a one line docstring. I’ve seen getter functions with one line docstrings. \$\endgroup\$ – Benjamin Philippe Dec 4 '17 at 14:18
  • \$\begingroup\$ Well of course you could, but I don't really see the point of that. \$\endgroup\$ – naiveai Dec 5 '17 at 12:22
  • \$\begingroup\$ I think just to have it in the documentation. \$\endgroup\$ – Benjamin Philippe Dec 5 '17 at 12:26
  • \$\begingroup\$ Mindlessly documenting doesn't do much good. \$\endgroup\$ – naiveai Dec 5 '17 at 12:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.