Point

from collections import namedtuple
import math


BasePoint = namedtuple('BasePoint', 'x y z')


class Point(BasePoint):

    """Three-dimensional point."""

    def distance(self, other):
        """Calculate the distance between two points."""
        a1 = self[0]
        b1 = self[1]
        c1 = self[2]
        a2 = other[0]
        b2 = other[1]
        c2 = other[2]
        return math.sqrt((a1-a2)**2+(b1-b2)**2+(c1-c2)**2)

    def shift(self, other):
        """Return new copy of our point, shifted by other."""
        a = self[0] + other[0]
        b = self[1] + other[1]
        c = self[2] + other[2]
        new_point = Point(a, b, c)
        return new_point

    def scale(self, value):
        """Scale Point by the given numeric value."""
        new_point = Point(self[0] * value, self[1] * value, self[2] * value)
        return new_point


if __name__ == "__main__":
    p1 = Point(1, 2, 3)
    p2 = Point(4, 5, 6)
    assert p2.shift(p1) == p1.shift(p2) == Point(5, 7, 9)
    assert p1.scale(2) == Point(2, 4, 6)
    assert p1.distance(p2) == p2.distance(p1) == 5.196152422706632
    print("Tests passed.")
  1. Use tuple unpacking to shorten code in distance and shift: “Readability counts.” and “Complex is better than complicated.”

  2. Split logic-heavy distance return line into two lines (using unpacking again): “Readability counts.”

  3. Remove unnecessary variables that are immediately returned: “Simple is better than complex.” and “Beautiful is better than ugly.”

  4. Use ability of namedtuple to reference named attributes to refactor scale function: “Explicit is better than implicit.”

  5. Rewrite scale line, swapping order of arithmetic to be more readable: “Readability counts.”

  6. Change shift and scale into more Pythonic + and * operators: “Complex is better than complicated.”

class Point(BasePoint):

    """Three-dimensional point."""

    def distance(self, other):
        """Calculate the distance between two points."""
        a1, b1, c1 = self
        a2, b2, c2 = other
        x, y, z = (a1 - a2), (b1 - b2), (c1 - c2)
        return math.sqrt(x**2 + y**2 + z**2)

    def __add__(self, other):
        """Return new copy of our point, shifted by other."""
        a1, b1, c1 = self
        a2, b2, c2 = other
        return Point((a1+a2), (b1+b2), (c1+c2))

    def __mul__(self, scalar):
        """Scale Point by the given numeric value."""
        return Point(scalar*self.x, scalar*self.y, scalar*self.z)

    def __rmul__(self, value):
        """Scale Point by the given numeric value."""
        return self.__mul__(value)

Our new tests:

if __name__ == "__main__":
    p1 = Point(1, 2, 3)
    p2 = Point(4, 5, 6)
    assert p2 + p1 == p1 + p2 == Point(5, 7, 9)
    assert p1 * 2 == 2 * p1 == Point(2, 4, 6)
    assert p1.distance(p2) == p2.distance(p1) == 5.196152422706632
    print("Tests passed.")