Shiklo Tools
PDF40
Image20
Calculators & Converters25
Text & Writing13
Developer26
Video14
Audio14
Document14
Data & Spreadsheet12
Web & SEO9
OCR & Scanning5
Archive & File12
Security & Privacy23
Health & Medical29
QR & Barcode5

Python Polymorphism and Encapsulation

Polymorphism means "many shapes": different classes can be used in the same way because they offer methods with the same names. Encapsulation means keeping an object's internal data behind its methods, so the rest of the program uses the object without depending on how it works inside. Together they make object-oriented programs flexible and safe to change.

One loop, many classes

import math

class Square:
    def __init__(self, side):
        self.side = side
    def area(self):
        return self.side ** 2

class Circle:
    def __init__(self, r):
        self.r = r
    def area(self):
        return math.pi * self.r ** 2

class Triangle:
    def __init__(self, base, height):
        self.base, self.height = base, height
    def area(self):
        return 0.5 * self.base * self.height

shapes = [Square(3), Circle(1), Triangle(4, 5)]
for s in shapes:
    print(f"{type(s).__name__:8} {s.area():6.2f}")
print("Total:", round(sum(s.area() for s in shapes), 2))
Output
Square     9.00
Circle     3.14
Triangle  10.00
Total: 22.14

The loop does not care which class each object belongs to; it only needs an area method. Adding a new shape class later needs no change to the loop.

Duck typing

These classes do not even share a parent. Python checks only that the method exists when it is called: "if it walks like a duck and quacks like a duck, it is a duck". Built-in functions work the same way: len works on anything that has a length, and for works on anything that can be looped over.

Private attributes by convention

Python has no truly private attributes. Instead, a name starting with an underscore, such as _balance, tells other programmers "this is internal, do not use it directly". Methods form the public way in.

Properties

A property looks like a plain attribute from outside, but runs a method when it is read or set. That lets a class check values without changing how it is used:

class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius          # goes through the setter below

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError("below absolute zero")
        self._celsius = value

    @property
    def fahrenheit(self):
        return self._celsius * 9 / 5 + 32

t = Temperature(25)
print(t.celsius, t.fahrenheit)
t.celsius = 100
print(t.fahrenheit)
try:
    t.celsius = -300
except ValueError as e:
    print("Error:", e)
Output
25 77.0
212.0
Error: below absolute zero

fahrenheit has no setter, so it is read-only: it is always worked out from the stored Celsius value and can never get out of step.

Practice exercises

  1. Add a Rectangle class with an area method to the shapes example and check that the loop still works.
  2. Write classes Dog, Cat and Duck with a sound method, and make each one speak in a loop.
  3. Give a Product class a price property that refuses negative prices.
  4. Add a read-only property kelvin to Temperature.