Encapsulation, Properties and Abstract Classes
50 min · Intermediate
Need help? Ask a tutor on WhatsApp (opens in a new tab)These questions test whether you understand Python's own rules instead of repeating other languages. Expect "does Python have private", "why properties" and "why no overloading".
Privacy by convention, not by lock
Name mangling is how Python renames an attribute written as __name inside a class body to _ClassName__name. It avoids accidental clashes in subclasses; it does not hide data.
A leading underscore says "internal, please do not touch". Two leading underscores trigger name mangling. Neither stops access.
class Account: def __init__(self): self._balance = 100 # internal by convention self.__pin = 1234 # name-mangled
a = Account()print(vars(a))print(a._Account__pin)Example 1 · Mangled names are still reachable
class A: def __init__(self): self.__x = 1
a = A()try: print(a.__x)except AttributeError: print("blocked by name")print(a._A__x)print([n for n in vars(a)])blocked by name 1 ['_A__x']
What this code does
a.__xfails only because the real name is different.- Anyone who knows the pattern can still read it, so treat it as a clash guard.
Python renames __pin to _Account__pin so a subclass cannot clash with it by accident. It is a protection against name clashes, not a security feature.
Properties
Property is a built-in decorator that turns a method into an attribute. Reads call the getter, writes call the setter, and you can add validation or computed values.
A property is a method that reads like an attribute. You can start with a plain attribute and add checks later without changing a single caller.
class Account: def __init__(self, balance): self._balance = balance
@property def balance(self): return self._balance
@balance.setter def balance(self, value): if value < 0: raise ValueError("negative") self._balance = valueExample 2 · A property with validation and a computed value
class Rectangle: def __init__(self, w, h): self.w = w self._h = h
@property def h(self): return self._h
@h.setter def h(self, value): if value <= 0: raise ValueError("height must be positive") self._h = value
@property def area(self): return self.w * self._h
r = Rectangle(3, 4)print(r.area)r.h = 5print(r.area)try: r.h = 0except ValueError as e: print(e)12 15 height must be positive
What this code does
areahas no setter, so it is computed each time and cannot be assigned.- Callers still write
r.h = 5, exactly as for a plain attribute.
acct.balance calls the getter and acct.balance = 5 calls the setter. Without a setter the property is read-only.
Abstract classes
Abstract base class is a class that inherits from ABC and declares abstract methods. A subclass must implement all of them before it can be instantiated.
An abstract class defines a contract that subclasses must fill in. It cannot be created itself.
from abc import ABC, abstractmethod
class Shape(ABC): @abstractmethod def area(self): ...
class Square(Shape): def __init__(self, side): self.side = side
def area(self): return self.side ** 2Example 3 · An abstract class and its contract
from abc import ABC, abstractmethod
class Shape(ABC): @abstractmethod def area(self): ...
def describe(self): return f"{type(self).__name__} with area {self.area()}"
class Square(Shape): def __init__(self, side): self.side = side
def area(self): return self.side ** 2
class Broken(Shape): pass
print(Square(3).describe())for cls in (Shape, Broken): try: cls() except TypeError: print("cannot create", cls.__name__)Square with area 9 cannot create Shape cannot create Broken
What this code does
describeis a normal method that uses the abstractarea, so every subclass gets it.- A subclass that skips
areastays abstract and cannot be created.
Example 4 · There is no overloading, so the last def wins
class Calc: def add(self, a): return a
def add(self, a, b): return a + b
def area(w, h=None): return w * w if h is None else w * h
print(Calc().add(2, 3))try: Calc().add(2)except TypeError: print("first add is gone")print(area(3), area(3, 4))5 first add is gone 9 12
What this code does
- The second
def addreplaced the first, so only the two-argument version exists. - A default argument gives one function two call shapes.
*argsandfunctools.singledispatchcover other needs.
Shape() raises TypeError because area is abstract. Square is complete, so Square(3) works.
Next step
Check your understanding
12 quick questions