Unit 3: OOP concepts
I. Orientation — Object-Oriented Programming in Python
Object-oriented programming (OOP) organizes software around objects that combine state and behavior. Python implements OOP through classes, instances, attributes, and methods; almost every Python value, including an integer, function, or list, is an object belonging to a class.
A. Governing Principle
The central principle of OOP is to model a program as cooperating objects, each responsible for managing related data and operations.
- Class: A blueprint defining the attributes and methods shared by a category of objects; for example,
class Accountdescribes what every account stores and does. - Object or instance: A concrete value created from a class;
account = Account()creates an instance ofAccount. - State: The data associated with an object, normally stored in instance attributes such as
account.balance. - Behavior: The operations an object can perform, represented by methods such as
account.deposit(500). - Identity: Each object has a distinct identity even when its state equals another object’s state;
a is btests whether two names refer to the same object. - Class convention: Python class names normally use
CapWords, while methods and attributes usesnake_case. - Object lifecycle:
__new__()creates an instance and is inherited fromobjectin ordinary classes.__init__()initializes the newly created instance.- Python’s garbage collector reclaims an object when it is no longer reachable.
- Root class: Classes ultimately inherit behavior from Python’s built-in
objectclass, directly or indirectly. - Member access: The dot operator selects an attribute or method, as in
student.nameorstudent.display().
II. OOP Features — Structure and Interaction of Objects
Python’s object model supports modular design by placing related responsibilities in classes and allowing objects to interact through well-defined interfaces.
A. OOP features
The principal OOP features explain how classes model entities, hide complexity, reuse behavior, and provide interchangeable interfaces.
- Classes and objects: A class defines a new type, while its instances hold independent state.
class Rectangle:
category = "quadrilateral" # Class attribute
def __init__(self, width, height):
self.width = width # Instance attribute
self.height = height
def area(self):
return self.width * self.height
r1 = Rectangle(4, 3)
r2 = Rectangle(8, 2)
print(r1.area()) # 12
print(r2.area()) # 16selfrefers to the instance on which a method is called.categorybelongs to the class and is shared unless shadowed by an instance attribute.widthandheightbelong separately tor1andr2.
- Abstraction: A class exposes essential operations while concealing unnecessary implementation details; a caller can use
r1.area()without knowing how the multiplication is performed. - Encapsulation: Data and the methods operating on it are grouped inside one class; an
Accountcan manage its own balance rather than allowing unrelated code to control it freely. - Inheritance: A new class can derive attributes and methods from an existing class;
SavingsAccount(Account)represents an “is-a” relationship. - Polymorphism: Different object types can respond to the same operation in type-appropriate ways; both
Circle.area()andRectangle.area()may provide anarea()interface. - Dynamic binding: Python determines the method implementation from the actual object at runtime. If
shaperefers to aCircle,shape.area()invokesCircle.area(). - Message passing: Objects communicate through method calls. In
account.withdraw(200), the request and argument are sent to theaccountobject. - Composition: One object can contain another to represent a “has-a” relationship; a
Carmight store anEngineobject rather than inherit from it. - Reusability: A tested class can be instantiated or extended in several programs, reducing duplicate code.
- Modularity: Each class can represent one coherent responsibility, making faults and changes easier to isolate.
B. Applications and limitations
OOP is most effective when a system contains entities with meaningful state and behavior, but it is not automatically the best design for every program.
-
Applications:
- Graphical applications: Classes such as
Window,Button, andMenumodel interface components with distinct state and event-handling behavior. - Business systems:
Customer,Invoice, andPaymentobjects map naturally to domain entities. - Simulations and games: Objects such as
PlayerandVehicleretain changing state across many operations. - Frameworks: Web and GUI frameworks use inheritance, composition, and polymorphic callbacks to let developers customize behavior.
- Graphical applications: Classes such as
-
Limitations:
- Unnecessary complexity: Wrapping a short calculation in several classes can be less readable than a function.
- Mutable state: Many objects changing one another’s attributes can make program behavior difficult to trace.
- Over-engineering: Deep inheritance hierarchies and excessive abstraction increase coupling rather than reducing it.
- Design alternative: Procedural or functional styles may suit data transformations, scripts, and stateless computations better; Python allows these styles to coexist with OOP.
III. Encapsulation — Controlling Object State
Encapsulation combines state with the operations responsible for that state and limits dependence on internal representation. Python enforces some boundaries through mechanisms such as properties, while other boundaries depend on naming conventions and programmer discipline.
A. Encapsulation
Encapsulation protects class invariants by directing access through a controlled public interface instead of exposing every implementation detail.
- Public members: Names such as
balanceanddeposit()are freely accessible and form the class’s normal interface. - Protected-name convention: A single leading underscore, as in
_balance, signals that a member is intended for internal or subclass use; Python does not prevent external access. - Name mangling: An identifier beginning with two underscores, such as
__pin, is transformed to a name containing the class name, approximately_ClassName__pin.- It reduces accidental clashes in subclasses.
- It is not a security mechanism because the transformed attribute can still be accessed deliberately.
- Property: The
propertymechanism lets attribute-style syntax invoke methods, allowing validation without changing the public interface. - Invariant: A condition that should remain true for every valid object; an account may require
balance >= 0. - Getter: A property method returns controlled information about internal state.
- Setter: A property method validates or transforms a proposed value before storing it.
- Information hiding: Callers depend on what an object promises to do, not on whether it stores a value directly, computes it, or retrieves it elsewhere.
class BankAccount:
def __init__(self, opening_balance=0):
if opening_balance < 0:
raise ValueError("Opening balance cannot be negative")
self._balance = opening_balance
@property
def balance(self):
return self._balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("Deposit must be positive")
self._balance += amount
def withdraw(self, amount):
if amount <= 0 or amount > self._balance:
raise ValueError("Invalid withdrawal")
self._balance -= amount
account = BankAccount(1000)
account.deposit(250)
account.withdraw(400)
print(account.balance) # 850- Concrete effect: Only
deposit()andwithdraw()modify_balance, so both operations can preserve the non-negative balance invariant. - Read-only interface: Because
balancehas no@balance.setter, an assignment such asaccount.balance = 0raisesAttributeError. - Exception use:
ValueErrorreports that the supplied amount has an unacceptable value, while leaving the object in its previous valid state.
B. Applications and limitations
Effective encapsulation reduces coupling, although Python intentionally favors cooperative interfaces rather than absolute access restrictions.
-
Applications:
- Validation: A
Temperatureproperty can reject values below an accepted physical or application-specific boundary. - Stable interfaces: An attribute can later become a computed property while callers continue using
object.attribute. - Maintenance: Internal storage may change from
_balanceto transaction records without changingdeposit()orwithdraw(). - Testing: Tests can target public behavior and invariants rather than fragile implementation details.
- Validation: A
-
Limitations:
- No strict private access modifier: Python has no Java-style
privatekeyword that makes an attribute completely inaccessible. - Property overuse: Trivial getters and setters add little value when no validation, computation, or compatibility requirement exists.
- False security: Name mangling discourages accidental access but cannot protect secrets from code running in the same process.
- Subclass constraints: Excessively hidden implementation details can make legitimate extension difficult; documented protected hooks are often preferable.
- No strict private access modifier: Python has no Java-style
IV. Inheritance — Reusing and Specializing Behavior
Inheritance creates a new class from one or more existing classes. The derived class receives accessible behavior from its base classes and may add, replace, or extend that behavior.
A. Inheritance
Inheritance models a genuine “is-a” relationship and enables objects from related classes to be used through a common interface.
- Base class: The class supplying inherited behavior, such as
Employee. - Derived class: The specialized class, such as
Manager(Employee). - Method overriding: A derived class defines a method with the same name to replace the inherited implementation.
super(): This function delegates a method call according to the method resolution order, commonly allowing a subclass to extend base initialization.- Method resolution order (MRO): The ordered sequence in which Python searches classes for an attribute;
ClassName.mro()displays that sequence. - Subtype polymorphism: Code expecting an
Employee-like interface can also operate on aManagerthat supplies the required methods.
class Employee:
def __init__(self, name, salary):
self.name = name
self.salary = salary
def describe(self):
return f"{self.name}: employee"
class Manager(Employee):
def __init__(self, name, salary, department):
super().__init__(name, salary)
self.department = department
def describe(self):
return f"{self.name}: manager of {self.department}"
worker = Employee("Asha", 40000)
leader = Manager("Ravi", 60000, "Sales")
print(worker.describe()) # Asha: employee
print(leader.describe()) # Ravi: manager of Sales- Single inheritance: One derived class has one direct base class, as in
Manager(Employee). - Multilevel inheritance: A class derives from an already derived class, such as
SeniorManager(Manager). - Hierarchical inheritance: Several classes derive from one base, such as
Manager(Employee)andDeveloper(Employee). - Multiple inheritance: A class has several direct bases, as in
SmartDevice(Camera, Phone); Python’s MRO resolves lookup order. - Interface checking:
isinstance(leader, Employee)returnsTrue, whileissubclass(Manager, Employee)also returnsTrue.
B. Applications and limitations
Inheritance is useful for stable subtype relationships, but composition is often safer when behavior merely needs to be assembled.
-
Applications:
- Shared implementation: Common initialization and methods remain in one base class instead of being copied.
- Specialization:
Manageraddsdepartmentwhile retaining the state supplied byEmployee. - Framework extension: A user-defined class can override framework methods such as event handlers.
- Uniform processing: A collection of different subclasses can be processed by calling a shared method such as
describe().
-
Limitations:
- Tight coupling: A subclass may rely on base-class details, so a base change can break several descendants.
- Fragile hierarchies: Deep inheritance chains make it difficult to identify where behavior originates.
- Multiple-inheritance ambiguity: Classes sharing ancestors can create complex initialization paths; cooperative
super()calls must follow compatible signatures. - Composition preference: If a
Carmerely uses anEngine, storing an engine object better represents the relationship than declaringCar(Engine).
Did this save you a night before the exam?
LPU Notes is free, and it stays free. Ads cover part of the server bill. The rest comes out of a student's own pocket: the domain, the storage, and keeping the site up through the weeks everyone needs it at once.
The payment button didn't load. An ad blocker or a filtered network is the usual reason. to try again.
Nothing here is ever locked, and nothing unlocks. Chip in only if it was worth it. What it pays for →