Unit 4: More on OOP concepts
I. Orientation — Polymorphism in Python
Polymorphism is the object-oriented principle by which one interface can represent different implementations or behaviors. In Python, it is supported by dynamic typing, inheritance, special methods, and runtime method resolution rather than by the compile-time mechanisms common in statically typed languages.
- Core meaning: The word polymorphism means “many forms”; an operation such as
obj.draw()can behave differently depending on the class ofobj. - Dynamic binding: Python usually determines the method to execute at runtime from the actual object, not merely from the variable name or annotation.
- Duck typing: An object is often accepted because it supports the required operation, regardless of its declared class—“if it behaves like the required object, it can be used as one.”
- Class namespace convention: A class normally stores only one attribute under a given name. Defining
calculatetwice does not create two overloads; the later definition replaces the earlier one. - Special-method convention: Operators are connected to methods with double-underscore names, such as
+to__add__()and==to__eq__(). - Inheritance requirement: Method overriding occurs when a subclass supplies its own implementation of a method inherited from a superclass.
- Related but distinct mechanisms:
- Function overloading: One conceptual operation accepts different argument patterns.
- Operator overloading: A class defines how operators act on its instances.
- Method overriding: A subclass changes inherited behavior while retaining the method interface.
II. Function Overloading — One Operation, Multiple Call Patterns
A. Definition and Python’s Runtime Model
Function overloading is the use of one function name for operations that differ by the number, types, or arrangement of arguments. Python does not provide traditional compile-time overloading based solely on multiple function signatures; flexible parameters or runtime dispatch are used instead.
- Traditional model: Languages with signature-based overloading may distinguish
area(int)fromarea(float, float), but Python identifies a function in a namespace primarily by its name. - Replacement behavior: If two functions have the same name, the second assignment replaces the first.
def display(value):
return f"Value: {value}"
def display(value, unit):
return f"{value} {unit}"
# display(10) raises TypeError because only the second definition remains.- Error cause: After the second
def,displayrefers only to the two-parameter function; the earlier function object is no longer bound to that name. - Runtime flexibility: A single Python definition can still support several valid call forms through default values, variable-length arguments, or explicit dispatch.
B. Function overloading
Python simulates function overloading by writing one function that interprets different argument patterns or by using a dispatch facility.
-
Parameter-based techniques
- Default arguments: A parameter receives a predefined value when the caller omits it. In
power(base, exponent=2),baseis the number being raised andexponentis its power. - Variable positional arguments:
*argscollects additional positional arguments into a tuple, allowing calls with different argument counts. - Variable keyword arguments:
**kwargscollects additional named arguments into a dictionary. - Manual inspection:
len(args),isinstance(), or pattern matching can select behavior, although excessive type checking weakens duck typing.
- Default arguments: A parameter receives a predefined value when the caller omits it. In
-
Dispatch and annotation techniques
- Single dispatch:
functools.singledispatchselects an implementation according to the type of the first argument. - Static overload declarations:
typing.overloaddescribes alternative signatures to type checkers, but the decorated declarations do not perform runtime dispatch. - Concrete implementation: A sequence of
@overloaddeclarations must be followed by one ordinary implementation that handles every declared case.
- Single dispatch:
Worked example: flexible argument counts
def total(*values):
if not values:
return 0
return sum(values)
print(total()) # 0
print(total(4, 6)) # 10
print(total(1, 2, 3)) # 6- Symbol definitions:
valuesis the tuple of supplied numbers;*packs positional arguments;sum(values)adds the tuple’s elements. - Overloaded effect: The same name supports zero, two, or three arguments without creating multiple definitions.
- Interface benefit: Callers learn one operation,
total, rather than separate names such astotal_twoandtotal_three.
C. Applications and Limitations
Function overloading is useful when several call forms express the same conceptual operation, but the accepted forms must remain understandable.
- Suitable applications: Constructors, conversion functions, formatting functions, and numerical operations often need optional parameters or several input representations.
- Readability condition: Each accepted signature should have a clear meaning; unrelated behaviors should use separate function names.
- Ambiguity risk: A function containing many
isinstance()branches may become difficult to test and extend. - Type limitation:
singledispatchconsiders only the first argument’s runtime type, so it does not directly provide full multiple dispatch. - Error design: Unsupported combinations should raise an informative exception such as
TypeError("expected one or two numeric arguments"). - Mutable-default warning: Defaults such as
items=[]are created once, not once per call; useitems=Noneand create the list inside the function.
III. Operator Overloading — Custom Meaning for Built-in Operators
A. Definition and Special-Method Protocol
Operator overloading allows instances of user-defined classes to respond to operators such as +, -, *, <, and ==. Python translates an operator expression into calls to designated special methods.
- Translation rule: For
a + b, Python first attempts an addition method associated with the operands, principallya.__add__(b). - Common mappings:
a + bcorresponds to__add__.a - bcorresponds to__sub__.a * bcorresponds to__mul__.a == bcorresponds to__eq__.a < bcorresponds to__lt__.-acorresponds to__neg__.
- Protocol-based design: Special methods are normally invoked through operators—write
a + brather than callinga.__add__(b)directly. - Semantic expectation: An overloaded operator should preserve an intuitive meaning;
+should usually combine or add rather than perform an unrelated action.
B. Operator overloading
A class overloads an operator by implementing the corresponding special method and returning either an appropriate result or NotImplemented.
Worked example: vector addition
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
if not isinstance(other, Vector):
return NotImplemented
return Vector(self.x + other.x, self.y + other.y)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(2, 3)
v2 = Vector(4, 1)
print(v1 + v2) # Vector(6, 4)- Symbol definitions:
v1andv2are two-dimensional vectors;xis the horizontal component,yis the vertical component, andotheris the right-hand operand. - Component rule: If (v_1=(x_1,y_1)) and (v_2=(x_2,y_2)), their sum is:
v1 + v2 = (x1 + x2, y1 + y2)- New-object behavior:
__add__()returns a newVector, leaving both operands unchanged. - Unsupported operands: Returning
NotImplementedlets Python attempt a reflected operation such as the right operand’s__radd__()before raisingTypeError. - Reflected methods:
__radd__(),__rsub__(), and similar methods handle cases where the class instance appears on the operator’s right side. - In-place methods:
__iadd__()supportsa += b; it may mutateaor return a replacement object.
C. Applications and Limitations
Operator overloading makes domain objects concise and expressive, but careless definitions can make code misleading.
- Natural applications: Vectors, matrices, complex numbers, dates, monetary values, sets, and symbolic expressions have familiar operator semantics.
- Consistency requirement: If
a == bis true, hashing and ordering behavior should obey the relevant Python contracts; mutable value objects generally should not be hashable. - Comparison support:
__eq__()defines equality, while ordering may require__lt__(),__le__(),__gt__(), and__ge__(). - No new operators: Classes may customize existing Python operators but cannot invent symbols such as
<>+<>. - Precedence limitation: Overloading does not change grammatical precedence;
a + b * cstill evaluates multiplication before addition. - Maintainability rule: When no conventional operator meaning exists, a named method such as
merge()orconvert()is clearer.
IV. Method Overriding — Specialized Inherited Behavior
A. Definition and Method Resolution
Method overriding occurs when a subclass defines a method with the same name as an inherited method, causing subclass instances to use the specialized implementation. The selected method is found through the class’s method resolution order, or MRO.
- Inheritance condition: There must be a superclass-subclass relationship; two unrelated classes with equally named methods do not override one another.
- Runtime selection: If
animalrefers to aDog, thenanimal.speak()invokesDog.speak()when that override exists. - Interface continuity: The overriding method should usually accept arguments compatible with the parent method so callers can use subclass objects safely.
- MRO inspection:
ClassName.mro()shows the order in which Python searches classes for an attribute. - Multiple inheritance: Python uses the C3 linearization algorithm to produce a consistent MRO for classes with several parents.
B. Method overriding
Overriding lets a subclass preserve a common operation while changing its implementation to suit a more specific type.
Worked example: polymorphic method calls
class Shape:
def area(self):
raise NotImplementedError("Subclasses must define area()")
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
shape = Rectangle(5, 3)
print(shape.area()) # 15- Symbol definitions:
widthis the rectangle’s horizontal length,heightis its vertical length, andarea()returns their product. - Override point:
Rectangle.area()has the same method name asShape.area()but supplies concrete rectangle behavior. - Dynamic dispatch: Although
Rectangleinherits fromShape, the callshape.area()resolves to the subclass implementation. - Abstract intent: Raising
NotImplementedErrorcommunicates that the base implementation is incomplete; formal abstract classes can instead useabc.ABCand@abstractmethod. - Parent cooperation: An override may extend rather than replace inherited behavior by calling
super().method(...). super()meaning: It delegates to the next implementation in the MRO, which is especially important in cooperative multiple inheritance.
C. Applications and Limitations
Method overriding supports extensible class hierarchies when subclasses genuinely satisfy the contract established by their base class.
- Typical applications: Framework callbacks, graphical components, serializers, payment types, and shape hierarchies use a common method name with type-specific behavior.
- Substitution principle: A subclass object should remain usable wherever its superclass is expected; an override should not impose surprising extra requirements.
- Signature compatibility: Narrowing accepted inputs or changing the return meaning can break polymorphic callers even though Python permits the definition.
- State requirement: The override must preserve necessary superclass invariants, often by calling
super().__init__()during subclass initialization. - Name-mangling limitation: Methods beginning with two underscores, such as
__process, are name-mangled and are not overridden in the ordinary way. - Design boundary: If subclasses repeatedly violate the parent contract, composition—placing one object inside another—may be more appropriate than inheritance.
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 →