Object-Oriented Programming

Encapsulation

Encapsulation is the process of hiding a class's internal data to prevent the client from directly accessing it.


class Data():
    def __init__(self):
        # the understore prefix implies this property is private
        # clients manipulate this variable at their own risk
        self._nums = [1, 2, 3, 4, 5]

    # this method exposes the approved API
    # for interacting with the private state
    def change_data(self, i: int, n: int) -> None:
        self._nums[i] = n

Abstraction

Abstraction is the process of "taking away or removing characteristics from something to reduce it to a set of essential characteristics."


# abstract the concept of a point in 2-D space
# rather than duplicating the subtraction logic
class Point():
    def __init__(self, x, y):
        self.coordinates = (x,y)

    def __sub__(self, b: "__class__") -> "__class__":
        return Point(self.coordinates[0] - b.coordinates[0], self.coordinates[1] - b.coordinates[1])

class Line():
    def __init__(self, start: Point, end: Point):
        self.start = start
        self.end = end

    def magnitude(self) -> float:
        diff = self.start - self.end
        return abs(diff.coordinates[1]) / abs(diff.coordinates[0])


class Rect():
    def __init__(self, top_left: Point, bottom_right: Point):
        self.top_left = top_left
        self.bottom_right = bottom_right

    def area(self) -> float:
        diff = self.top_left - self.bottom_right
        return diff.coordinates[0] * diff.coordinates[1]

p1 = Point(1,2)
p2 = Point(3,5)
l = Line(p1, p2)
r = Rect(p1, p2)
print(l.magnitude())
# 1.5
print(r.area())
# 6
      

Polymorphism

Polymorphism is the ability in programming to present the same interface for different data types.


from random import choice

# define different classes which all have a common API
class Foo():
    def act(self):
        print("foo")

class Bar():
    def act(self):
        print("bar")

class Qux():
    def act(self):
        print("qux")

# randomly select one of several different classes...
instances = [Foo(), Bar(), Qux()]
for _ in range(10):
    instance = choice(instances)
    # ...and invoke the same code irrespective of the underlying implementation.
    instance.act()

Inheritance

Classes can inherit methods and variables from another class.


class Parent_Example():
    def some_method(self):
        return "This method was defined in the parent."
    def some_other_method(self):
        return "This method was also defined in the parent."

class Child_Example(Parent_Example):
    def some_other_method(self):
        return super().some_other_method() + " But then overriden in the child."

c = Child_Example()
print(c.some_method())
# This method was defined in the parent.
print(c.some_other_method())
# This method was also defined in the parent. But then overriden in the child.