OOP: Classes & Objects

Difficulty: Intermediate

Question

Explain OOP in Python. How do classes, objects, and encapsulation work?

Answer

Python supports OOP with classes as blueprints and objects as instances.

Key concepts: - Class: template defining attributes and methods - Object/Instance: concrete realization of a class - __init__: constructor method, called when creating an instance - self: reference to the current instance (explicit in Python) - Encapsulation: controlling access via naming conventions (_private, __mangled)

Python uses naming conventions rather than access modifiers: - public: any_name (accessible from anywhere) - protected: _name (convention: internal use, still accessible) - private: __name (name mangling: _ClassName__name)

Code examples

Classes and Instances

class BankAccount:
    # Class variable: shared by all instances
    bank_name = "Python Bank"
    _total_accounts = 0
    
    def __init__(self, owner, balance=0):
        # Instance variables: unique to each instance
        self.owner = owner
        self._balance = balance  # protected by convention
        self.__pin = 1234       # name-mangled (private)
        BankAccount._total_accounts += 1
    
    def deposit(self, amount):
        if amount > 0:
            self._balance += amount
            return f"Deposited ${amount}. Balance: ${self._balance}"
        raise ValueError("Amount must be positive")
    
    def __str__(self):
        return f"{self.owner}'s account: ${self._balance}"
    
    @classmethod
    def get_total_accounts(cls):
        return cls._total_accounts
    
    @staticmethod
    def validate_amount(amount):
        return isinstance(amount, (int, float)) and amount > 0

acc = BankAccount("Alice", 1000)
print(acc)                          # Alice's account: $1000
print(acc.deposit(500))             # Deposited $500. Balance: $1500
print(BankAccount.get_total_accounts())  # 1
print(BankAccount.validate_amount(100))  # True

Class variables are shared across instances. Instance variables (set in __init__) are unique per object. @classmethod receives the class, @staticmethod receives nothing.

Properties and Encapsulation

class Temperature:
    def __init__(self, celsius=0):
        self._celsius = celsius  # store internally
    
    @property
    def celsius(self):
        """Getter: accessed like an attribute."""
        return self._celsius
    
    @celsius.setter
    def celsius(self, value):
        """Setter: validates before setting."""
        if value < -273.15:
            raise ValueError("Below absolute zero!")
        self._celsius = value
    
    @property
    def fahrenheit(self):
        """Computed property: read-only."""
        return self._celsius * 9/5 + 32

t = Temperature(25)
print(t.celsius)      # 25 (calls getter)
print(t.fahrenheit)   # 77.0 (computed)
t.celsius = 100       # calls setter
print(t.fahrenheit)   # 212.0
# t.celsius = -300    # ValueError: Below absolute zero!
# t.fahrenheit = 50   # AttributeError: can't set (no setter)

@property lets you use methods like attributes, adding validation and computed values. This is Python's preferred encapsulation pattern over explicit getters/setters.

Name Mangling and Introspection

class Secret:
    def __init__(self):
        self.public = "visible"
        self._protected = "convention only"
        self.__private = "name-mangled"

s = Secret()
print(s.public)        # 'visible'
print(s._protected)    # 'convention only' (still accessible!)
# print(s.__private)   # AttributeError!
print(s._Secret__private)  # 'name-mangled' (mangled name)

# dir() shows all attributes
print([attr for attr in dir(s) if not attr.startswith('__')])
# ['_Secret__private', '_protected', 'public']

# isinstance and type checking
print(isinstance(s, Secret))  # True
print(type(s))               # <class '__main__.Secret'>

# __dict__ shows instance attributes
print(s.__dict__)
# {'public': 'visible', '_protected': 'convention only', '_Secret__private': 'name-mangled'}

Python has no true private attributes. __name becomes _ClassName__name (name mangling). The convention is: _ means 'please don't touch', __ means 'really don't touch'.

Key points

Concepts covered

Classes, Objects, Encapsulation, Class vs Instance, __init__, Properties