Python

P
PythonHub
Progress 0%

Python

  • Home
  • History of Python
  • Applications of Python
  • Introduction To Python
    • What is Python
    • Character Set
    • Tokens in Python
    • Python Execution Mode
    • Variable And Identifiers
    • Data Types in Python
    • Operators And Expressions
    • Constants in Python
    • Assignment Statement
    • Input / Output in Python
    • Simple Python Scripts
    • Namespace in Python
    • 📝 Assignments
  • Operators in Python
    • Arithmetic Operators
    • Assignment Operators
    • Shorthand Assignment Operators
    • Relational Operators
    • Logical Operators
    • Bitwise Operators
    • Special Operators
    • 📝 Assignments
  • Input Output in Python
    • Accept Input
    • Output Formatting
    • 📝 Assignments
  • Conditional Statement
    • Decision Making
    • if Statement
    • IF-ELSE STATEMENT
    • IF-ELSE LADDER
    • NESTED IF-ELSE
    • Short Hand IF-ELSE
    • 📝 Assignments
  • Loops
    • Introduction to Loops
    • While Loop
    • Nested While Loop
    • 📝 While Loop Assignments
    • For Loop
    • For Loop Examples
    • Nested For Loop
    • Nested For Loop Examples
    • Infinite While Loops
    • Infinite For Loops
    • Break, Continue and Else in Loops
    • Difference Between For and While Loop
    • For Each Loop
    • 📝 For Each Assignments
    • 📝 All Loops Assignments
  • List
    • List in Python
    • Access List Elements
    • List Functions
    • Iterate (Loop) List
    • List Comprehension
    • 📝 Assignments
  • Tuple
    • Tuple in Python
    • Access Tuple Elements
    • Tuple Functions
    • Iterate (Loop) Tuple
    • Unpack Tuple
    • Tuple Comprehension
    • 📝 Assignments
  • Set
    • Set in Python
    • Access Set Elements
    • Set Methods
    • Iterate (Loop) Set
    • Pack/Unpack Set
    • Set Comprehension
    • 📝 Assignments
  • Dictionary
    • Dictionary
    • Access Dictionary Items
    • Dictionary Methods
    • Iterate (Loop) Dictionary
    • Formatting Dictionaries
    • Nested Dictionaries
    • Dictionary Comprehension
    • 📝 Assignments
  • Diff List Tuple Set Dictionary
    • List vs Tuple
    • List vs Set
    • List vs Dictionary
    • Tuple vs Set
    • Tuple vs Dictionary
    • Dictionary vs Set
    • 📝 Assignments
  • Exception
    • Error vs Exception
    • Types of Exception
    • Exception Handling
    • User Defined Exception
    • Logging Exception
    • 📝 Assignments
  • Functions
    • Introduction to Functions
    • Modular Programming
    • Types of Functions
    • Inbuilt Functions
    • Need For User-Defined Function
    • Elements of User Defined Function
    • Function Arguments
    • Nesting of Functions
    • Recursion
    • Global Local and Non Local
    • Python Lambda Functions
    • 📝 Assignments
  • Python Module
    • Introduction to Module
    • Inbuilt Modules in Python
    • User Defined Module
    • 📝 Assignments
  • File Handling
    • Introduction to Files
    • Create File
    • Read Files
    • Write to File
    • Rename File
    • Copy File
    • Move File
    • List Files in Directory
    • Binary Files
    • Zipping and Unzipping Files
    • 📝 Assignments
  • Strings
    • Basics of Strings
    • String Special Operators
    • String Formatting Operators
    • String Methods
    • 📝 Assignments
  • Regular Expressions
  • Python OOPS
    • Basics of Object Oriented
    • What are Classes and Objects?
    • Creating Class and Object
    • OOP vs Procedural Programming
    • Difference Between Classes and Objects
    • Constructors
    • Destructor
    • Built Class Methods and Attributes
    • Class and Instance Variables
    • Inheritance in Python
    • Single Inheritance
    • Multiple Inheritance
    • Multilevel Inheritance
    • Hierarchical Inheritance
    • Hybrid Inheritance
    • Abstraction
    • Method Overriding
    • Abstract Method
    • Interfaces in Python
    • Abstract Class vs Interface
    • Public, Private and Protected
    • Overloading vs Overriding
    • Inheritance vs Composition
    • Encapsulation
    • Polymorphism
    • Inner Classes
    • 📝 Assignments
  • Advanced Python
    • Iterator in Python
    • Generator in Python
    • Decorator in Python
    • Type Hints in Python
    • Async/Await Programming
    • Dataclasses in Python
    • Context Managers in Python
    • Property Decorator in Python
    • __slots__ in Python
    • Enums in Python
    • Walrus Operator in Python
    • Match-Case in Python
    • 📝 Assignments
  • Python Standard Library
    • Collections Module
    • Itertools Module
    • Functools Module
    • Datetime Module
    • JSON Module
    • OS Module
    • Sys Module
    • Random Module
    • Math Module
    • 📝 Assignments
  • Python Testing
    • Unit Testing in Python
    • Pytest Framework
    • Mocking in Python
    • 📝 Assignments
  • Python Best Practices
    • PEP 8 Style Guide
    • Docstrings in Python
    • Logging in Python
    • Code Optimization Tips
    • Debugging Techniques
    • 📝 Assignments
  • MySQL Database in Python
    • Introduction to MySQL with Python
    • DBMS vs File System
    • Connecting to MySQL Database
    • Create Database in MySQL
    • Create Table in MySQL
    • Insert Data in MySQL
    • Insert Multiple Rows
    • Select Data from MySQL
    • WHERE Clause in MySQL
    • Update Data in MySQL
    • Delete Data from MySQL
    • Parameterized Queries
    • Transaction Management
    • Error Handling
    • Connection Pooling
    • MySQL Drivers Guide
    • Joins in MySQL
    • Aggregation Functions
    • Backup MySQL Database
    • Best Practices
    • 📝 Assignments
  • MySQL Database Operations
    • SELECT Statement
    • MySQL Operators
    • DDL Statements
    • DML Statements
    • Subqueries
    • JOIN Operations
    • Aggregation
    • Case Study
    • 📝 Assignments
  • Graphics in Python
  • Threads in Python
    • Introduction to Threads
    • Process vs Threads
    • Concurrent Programming & GIL
    • Uses of Threads
    • Creating Threads
    • Single Tasking
    • Multi Tasking
    • Thread Synchronization
    • 📝 Assignments
  • Interview Questions & Answers
  • Python Case Studies
  • Multiple Choice Questions
  • 📝 Practice Problems
Get in Touch
  • tech2dsm@gmail.com

© Sankalan Data Tech

Python Language Interactive Tutorial

Python: Overloading vs Overriding

Python Overloading vs Overriding - Key Differences

Learn the difference between method overloading and method overriding in Python.

Created by Sankalan Data Tech Team Verified
Data Engineers, Analysts, Scientists & Trainers
Created by experienced Python developers, data engineers, and data scientists to make programming easy through practical examples, real-world experience, and clear explanations.
On this page:
  • What's the Difference?
  • Method Overloading in Python
  • Method Overriding in Python
  • Key Differences
  • Real-World Example
  • Best Practices
  • Try It Yourself
  • Quick Quiz
  • Frequently Asked Questions
Share this tutorial:
Twitter LinkedIn Facebook WhatsApp Reddit Telegram Email Copy Link
What You'll Learn Here
  • Method Overloading — having multiple methods with the same name but different parameters
  • Method Overriding — redefining a parent class method in a child class
  • Key differences — when and how each is used
  • Python's approach — why Python handles overloading differently
  • Practical examples — see both concepts in real code

What's the Difference?

If you're coming from languages like Java or C++, you might be used to method overloading and method overriding being two sides of the same coin. But in Python, things work a bit differently.

Here's the quick version:

  • Overloading is about having multiple methods with the same name but different parameters, usually within the same class.
  • Overriding is about a child class redefining a method that it inherited from its parent class.

The tricky part? Python doesn't support traditional method overloading the way Java does. But don't worry — Python has its own ways to achieve similar results.

💡 Key concept: Overloading happens within a class. Overriding happens between a parent and child class. Overloading is about flexibility — overriding is about specialization.

Method Overloading in Python

1

What is Method Overloading?

Method overloading is when you define multiple methods with the same name but different parameters. For example, you might have a calculate() method that can accept either one number, two numbers, or three numbers.

In languages like Java, you'd write:

class Calculator {
    public int add(int a) { return a; }
    public int add(int a, int b) { return a + b; }
    public int add(int a, int b, int c) { return a + b + c; }
}

But Python doesn't work like that. If you try to define multiple methods with the same name, the last one overwrites the previous ones.

So how does Python handle overloading? Let's look at the three ways Python developers do it:

# Method Overloading in Python — The Three Ways

print("=" * 50)
print("METHOD OVERLOADING IN PYTHON")
print("=" * 50)

print("\n--- Approach 1: Default Arguments ---")
print("This is the simplest and most Pythonic way.")

class Calculator:
    def add(self, a, b=0, c=0):
        """Add up to three numbers using default arguments"""
        return a + b + c

calc = Calculator()
print(f"add(5): {calc.add(5)}")           # 5
print(f"add(5, 3): {calc.add(5, 3)}")     # 8
print(f"add(5, 3, 2): {calc.add(5, 3, 2)}")  # 10

print("\n Default arguments work great for simple overloading.")
print(" But you can't have different behavior based on parameter types.")


print("\n--- Approach 2: Variable Arguments (*args) ---")
print("Use *args when you don't know how many arguments you'll get.")

class FlexibleCalculator:
    def add(self, *args):
        """Add any number of arguments"""
        return sum(args)

flex = FlexibleCalculator()
print(f"add(5): {flex.add(5)}")                      # 5
print(f"add(5, 3): {flex.add(5, 3)}")                # 8
print(f"add(5, 3, 2, 1): {flex.add(5, 3, 2, 1)}")    # 11

print("\n *args is great when you need any number of arguments.")
print(" But you lose the ability to have different behavior.")


print("\n--- Approach 3: Manual Type Checking ---")
print("Use isinstance() to handle different types differently.")

class SmartCalculator:
    def add(self, a, b=None, c=None):
        """Add with different behavior based on what's passed"""
        
        # If only one argument, return it
        if b is None and c is None:
            return a
        
        # If two arguments, add them
        if c is None:
            return a + b
        
        # If three arguments, add them with extra logic
        return a + b + c

smart = SmartCalculator()
print(f"add(5): {smart.add(5)}")
print(f"add(5, 3): {smart.add(5, 3)}")
print(f"add(5, 3, 2): {smart.add(5, 3, 2)}")

print("\n Manual checking gives you full control.")
print(" It's more code and less Pythonic.")

print("\n--- Approach 4: Using functools.singledispatch ---")
print("For function overloading based on type.")

from functools import singledispatch

@singledispatch
def process(value):
    """Default behavior for any type"""
    return f"Processing unknown type: {value}"

@process.register(int)
def _(value):
    return f"Processing integer: {value * 2}"

@process.register(str)
def _(value):
    return f"Processing string: {value.upper()}"

@process.register(list)
def _(value):
    return f"Processing list with {len(value)} items"

print(f"process(5): {process(5)}")
print(f"process('hello'): {process('hello')}")
print(f"process([1, 2, 3]): {process([1, 2, 3])}")

print("\n singledispatch is great for type-based overloading.")
print(" It's more advanced and not used as often.")

Method overloading key points:

  • Default arguments — the simplest and most common approach
  • *args — when you need any number of arguments
  • Manual type checking — when you need different behavior for different types
  • singledispatch — Python's official way to overload functions based on type
  • No built-in overloading — Python doesn't support it natively like Java

Quick Check: Why doesn't Python support traditional method overloading? (Answer: Because methods are stored in dictionaries by name, and the last definition overwrites previous ones)

Method Overriding in Python

2

What is Method Overriding?

Method overriding is when a child class provides its own implementation of a method that it inherited from its parent class. It's about changing the behavior of an inherited method to suit the child class.

Unlike overloading, Python fully supports method overriding. It's one of the core features of inheritance and polymorphism in Python.

Think of it like a family recipe. Your parent has a way of making pasta sauce. You inherit that recipe, but you override it by adding your own twist — maybe more garlic, maybe some chili flakes. The method name is the same, but the implementation is different.

# Method Overriding in Python

print("=" * 50)
print("METHOD OVERRIDING IN PYTHON")
print("=" * 50)

class Animal:
    """Base class - Animal"""
    
    def __init__(self, name):
        self.name = name
    
    def make_sound(self):
        """Default sound method - the 'recipe'"""
        return "Some animal sound"
    
    def move(self):
        """Default move method"""
        return f"{self.name} moves somehow"
    
    def eat(self):
        """Default eat method"""
        return f"{self.name} eats something"

class Dog(Animal):
    """Child class - Dog overrides Animal's methods"""
    
    def make_sound(self):
        """Override: dog has a specific sound"""
        return f"{self.name} says: Woof! Woof!"
    
    def move(self):
        """Override: dog moves differently"""
        return f"{self.name} runs on four legs"

class Cat(Animal):
    """Child class - Cat overrides Animal's methods"""
    
    def make_sound(self):
        """Override: cat has a specific sound"""
        return f"{self.name} says: Meow!"
    
    def move(self):
        """Override: cat moves differently"""
        return f"{self.name} walks silently"

class Bird(Animal):
    """Child class - Bird overrides Animal's methods"""
    
    def make_sound(self):
        """Override: bird has a specific sound"""
        return f"{self.name} says: Chirp! Chirp!"
    
    def move(self):
        """Override: bird moves differently"""
        return f"{self.name} flies through the air"

# --- Demonstration ---
animals = [
    Dog("Rex"),
    Cat("Whiskers"),
    Bird("Tweety")
]

for animal in animals:
    print(f"\n--- {animal.__class__.__name__}: {animal.name} ---")
    print(f"Sound: {animal.make_sound()}")
    print(f"Move: {animal.move()}")
    print(f"Eat: {animal.eat()}")  # Uses the inherited method

print("\n" + "=" * 50)
print("KEY OBSERVATIONS:")
print("=" * 50)
print("✅ Each child overrode make_sound() and move()")
print("✅ The eat() method was inherited (not overridden)")
print("✅ The override changed the behavior while keeping the same method name")
print("✅ This is how polymorphism works in Python")

Method overriding key points:

  • Same name, different implementation — child class provides its own version
  • Inheritance required — you can only override what you inherit
  • Supports polymorphism — different objects can respond to the same method call differently
  • Fully supported — Python handles overriding naturally
  • Can call parent — use super() to access the parent's version

Quick Check: What's the difference between overriding and overloading? (Answer: Overriding is about redefining a parent's method in a child class; overloading is about having multiple versions in the same class)

Key Differences

3

Overloading vs Overriding — Side by Side

Let's compare these two concepts directly so you can see the differences at a glance.

# Overloading vs Overriding — Complete Comparison

from functools import singledispatch

print("=" * 60)
print("OVERLOADING vs OVERRIDING")
print("=" * 60)

# ============================================================
# EXAMPLE 1: OVERLOADING (within a class)
# ============================================================

class Printer:
    """A class that demonstrates overloading patterns"""
    
    # Approach: Using default arguments
    def print_message(self, msg, times=1):
        """Print a message multiple times"""
        result = []
        for i in range(times):
            result.append(f"{i+1}: {msg}")
        return "\n".join(result)
    
    # Approach: Using *args
    def print_all(self, *args):
        """Print all arguments"""
        return " | ".join(str(arg) for arg in args)
    
    # Approach: Manual type checking
    def process(self, data):
        """Process data differently based on type"""
        if isinstance(data, int):
            return f"Processing number: {data * 2}"
        elif isinstance(data, str):
            return f"Processing string: {data.upper()}"
        elif isinstance(data, list):
            return f"Processing list: {len(data)} items"
        else:
            return f"Processing: {data}"

# Using overloading within a class
printer = Printer()
print("\n--- OVERLOADING (within same class) ---")
print("print_message('Hello', 3):")
print(printer.print_message("Hello", 3))
print("\nprint_all(1, 2, 3, 4):", printer.print_all(1, 2, 3, 4))
print("process(5):", printer.process(5))
print("process('hello'):", printer.process("hello"))
print("process([1, 2]):", printer.process([1, 2]))


# ============================================================
# EXAMPLE 2: OVERRIDING (between parent and child)
# ============================================================

class Parent:
    """Parent class - has a default method"""
    
    def greet(self):
        """Default greeting method"""
        return "Hello from Parent"

class Child1(Parent):
    """Child 1 - overrides the greet method"""
    
    def greet(self):
        """Overridden greeting method"""
        return "Hello from Child 1 - I'm different!"

class Child2(Parent):
    """Child 2 - overrides the greet method"""
    
    def greet(self):
        """Overridden greeting method"""
        return "Hello from Child 2 - I'm also different!"

class Child3(Parent):
    """Child 3 - does NOT override the greet method"""
    
    pass  # Inherits the parent's greet()

# Using overriding between classes
print("\n--- OVERRIDING (between parent and child) ---")
parent = Parent()
child1 = Child1()
child2 = Child2()
child3 = Child3()

print(f"Parent.greet(): {parent.greet()}")
print(f"Child1.greet(): {child1.greet()}")
print(f"Child2.greet(): {child2.greet()}")
print(f"Child3.greet(): {child3.greet()}")  # Inherited from Parent


# ============================================================
# COMPARISON TABLE
# ============================================================

print("\n" + "=" * 60)
print("OVERLOADING vs OVERRIDING - COMPARISON TABLE")
print("=" * 60)
print("""
┌──────────────────────┬──────────────────────────────┬────────────────────────────────┐
│                       │      OVERLOADING             │          OVERRIDING             │
├──────────────────────┼──────────────────────────────┼────────────────────────────────┤
│ WHAT IT IS           │ Multiple versions of the     │ Redefining a parent method     │
│                       │ same method in one class     │ in a child class               │
├──────────────────────┼──────────────────────────────┼────────────────────────────────┤
│ WHERE IT HAPPENS     │ Within the SAME class        │ Between PARENT and CHILD class │
├──────────────────────┼──────────────────────────────┼────────────────────────────────┤
│ INHERITANCE          │ Not required                 │ Required - needs inheritance   │
├──────────────────────┼──────────────────────────────┼────────────────────────────────┤
│ WHEN TO USE          │ When you need flexibility    │ When you need specialization   │
│                       │ in how a method is called   │ of an inherited method         │
├──────────────────────┼──────────────────────────────┼────────────────────────────────┤
│ PYTHON SUPPORT       │ Not natively (but has        │ Full support - works naturally │
│                       │ workarounds)                │                                │
├──────────────────────┼──────────────────────────────┼────────────────────────────────┤
│ COMMON APPROACHES    │ Default arguments, *args,    │ Define method in child class   │
│                       │ singledispatch              │ with same name as parent       │
├──────────────────────┼──────────────────────────────┼────────────────────────────────┤
│ REASON FOR USE       │ Flexibility                 │ Specialization / customization │
├──────────────────────┼──────────────────────────────┼────────────────────────────────┤
│ CAN USE super()      │ N/A                         │ Yes, to call parent version    │
└──────────────────────┴──────────────────────────────┴────────────────────────────────┘
""")

print("\n REMEMBER:")
print("   • Overloading = Multiple ways to call a method in the SAME class")
print("   • Overriding = Changing a method's behavior in a CHILD class")

Key differences summary:

  • Location — overloading happens within a class; overriding happens between classes
  • Inheritance — overloading doesn't need inheritance; overriding requires it
  • Purpose — overloading is for flexibility; overriding is for specialization
  • Python support — overriding is fully supported; overloading needs workarounds
  • Key method — with overriding, you can use super() to call the parent version

Quick Check: What's the main purpose of method overriding? (Answer: To specialize or change the behavior of an inherited method to suit the child class)

Real-World Example

4

A Payment Processing System

Let's look at a real-world example that uses both concepts. We'll build a payment system where:

  • Overriding — different payment methods have their own process() implementation
  • Overloading — the create_payment() method can handle different arguments
# Real-World Example: Payment Processing System

from datetime import datetime
import json

print("=" * 60)
print("PAYMENT PROCESSING SYSTEM")
print("=" * 60)

# ============================================================
# BASE CLASS - Payment (uses overriding)
# ============================================================

class Payment:
    """Base class for all payment types"""
    
    def __init__(self, amount, currency="USD"):
        self.amount = amount
        self.currency = currency
        self.status = "pending"
        self.created_at = datetime.now()
        self.transaction_id = None
    
    def process(self):
        """Default payment processing (to be overridden)"""
        self.status = "processed"
        self.transaction_id = f"TXN-{int(datetime.now().timestamp())}"
        return f"Payment processed: {self.amount} {self.currency}"
    
    def get_details(self):
        """Get payment details"""
        return {
            "amount": self.amount,
            "currency": self.currency,
            "status": self.status,
            "created_at": self.created_at.isoformat(),
            "transaction_id": self.transaction_id
        }

# ============================================================
# CHILD CLASSES - Override process()
# ============================================================

class CreditCardPayment(Payment):
    """Credit card payment - overrides process()"""
    
    def __init__(self, amount, card_number, expiry, cvv, currency="USD"):
        super().__init__(amount, currency)
        self.card_number = "****" + card_number[-4:]  # Masked
        self.expiry = expiry
        self.cvv = "***"
    
    def process(self):
        """Override: Process credit card payment"""
        # Simulate credit card processing
        self.status = "authorized"
        self.transaction_id = f"CC-{int(datetime.now().timestamp())}"
        return f" Credit card payment: {self.amount} {self.currency} (Card: {self.card_number})"

class PayPalPayment(Payment):
    """PayPal payment - overrides process()"""
    
    def __init__(self, amount, email, currency="USD"):
        super().__init__(amount, currency)
        self.email = email
    
    def process(self):
        """Override: Process PayPal payment"""
        self.status = "completed"
        self.transaction_id = f"PP-{int(datetime.now().timestamp())}"
        return f" PayPal payment: {self.amount} {self.currency} (Email: {self.email})"

class CryptoPayment(Payment):
    """Cryptocurrency payment - overrides process()"""
    
    def __init__(self, amount, wallet_address, currency="BTC"):
        super().__init__(amount, currency)
        self.wallet_address = wallet_address
    
    def process(self):
        """Override: Process crypto payment"""
        self.status = "confirmed"
        self.transaction_id = f"CRYPTO-{int(datetime.now().timestamp())}"
        return f"₿ Crypto payment: {self.amount} {self.currency} (Wallet: {self.wallet_address[:8]}...)"

# ============================================================
# PAYMENT PROCESSOR - Uses overloading patterns
# ============================================================

class PaymentProcessor:
    """Handles payments with flexible methods"""
    
    def __init__(self):
        self.payments = []
    
    def create_payment(self, *args, **kwargs):
        """
        Create a payment - supports multiple argument patterns
        This demonstrates overloading using *args and **kwargs
        """
        
        # Pattern 1: Direct payment object
        if len(args) == 1 and isinstance(args[0], Payment):
            payment = args[0]
            self.payments.append(payment)
            return payment
        
        # Pattern 2: Type and amount with kwargs
        elif 'type' in kwargs and 'amount' in kwargs:
            payment_type = kwargs['type'].lower()
            amount = kwargs['amount']
            
            if payment_type == 'credit':
                card = kwargs.get('card_number', 'xxxx')
                expiry = kwargs.get('expiry', 'xx/xx')
                cvv = kwargs.get('cvv', 'xxx')
                payment = CreditCardPayment(amount, card, expiry, cvv)
            elif payment_type == 'paypal':
                email = kwargs.get('email', 'user@example.com')
                payment = PayPalPayment(amount, email)
            elif payment_type == 'crypto':
                wallet = kwargs.get('wallet', '0x0000')
                currency = kwargs.get('currency', 'BTC')
                payment = CryptoPayment(amount, wallet, currency)
            else:
                payment = Payment(amount)
            
            self.payments.append(payment)
            return payment
        
        else:
            raise ValueError("Invalid payment creation pattern")
    
    def process_all(self):
        """Process all pending payments"""
        results = []
        for payment in self.payments:
            if payment.status == "pending":
                result = payment.process()
                results.append(result)
        return results
    
    def get_summary(self):
        """Get summary of all payments"""
        total = sum(p.amount for p in self.payments)
        return {
            "total_payments": len(self.payments),
            "total_amount": total,
            "status_breakdown": {
                "pending": sum(1 for p in self.payments if p.status == "pending"),
                "processed": sum(1 for p in self.payments if p.status != "pending")
            }
        }

# ============================================================
# DEMONSTRATION
# ============================================================

print("\n1. CREATING PAYMENTS (overloading)")
processor = PaymentProcessor()

# Using pattern 1: Direct payment object
print("\n--- Pattern 1: Direct payment object ---")
card_payment = CreditCardPayment(100.00, "4111-1111-1111-1111", "12/25", "123")
processor.create_payment(card_payment)
print(f" Created: Credit card payment - ${card_payment.amount}")

# Using pattern 2: Type-based creation
print("\n--- Pattern 2: Type-based creation ---")
paypal = processor.create_payment(
    type='paypal',
    amount=50.00,
    email='customer@example.com'
)
print(f" Created: PayPal payment - ${paypal.amount}")

crypto = processor.create_payment(
    type='crypto',
    amount=0.05,
    wallet='0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
    currency='BTC'
)
print(f" Created: Crypto payment - {crypto.amount} {crypto.currency}")

# Just a regular payment
regular = processor.create_payment(
    type='regular',
    amount=75.00
)
print(f" Created: Regular payment - ${regular.amount}")

print("\n2. PROCESSING PAYMENTS (overriding)")
results = processor.process_all()
for result in results:
    print(f"   {result}")

print("\n3. PAYMENT SUMMARY")
summary = processor.get_summary()
print(f"   Total payments: {summary['total_payments']}")
print(f"   Total amount: ${summary['total_amount']}")
print(f"   Status: {summary['status_breakdown']}")

print("\n4. INDIVIDUAL PAYMENT DETAILS")
for i, payment in enumerate(processor.payments, 1):
    print(f"   Payment {i}: {payment.get_details()}")

print("\n" + "=" * 60)
print("KEY TAKEAWAYS:")
print("=" * 60)
print(" OVERRIDING: Each payment type overrides process()")
print(" OVERLOADING: create_payment() handles different argument patterns")
print(" Polymorphism: Each payment behaves differently but uses the same interface")

Real-world example key points:

  • Overriding — each payment type (CreditCard, PayPal, Crypto) overrides process()
  • Overloading — create_payment() accepts different argument patterns
  • Polymorphism — process_all() works with any payment type
  • Clean separation — base class defines the contract, children provide specific behavior

Quick Check: In the example, what's overridden and what's overloaded? (Answer: process() is overridden in each payment type; create_payment() is overloaded through *args and **kwargs)

Best Practices

5

Using Overloading and Overriding Effectively

# Best Practices for Overloading and Overriding

from functools import singledispatch

print("=" * 60)
print("BEST PRACTICES")
print("=" * 60)

# ============================================================
# 1. OVERLOADING BEST PRACTICES
# ============================================================

print("\n--- OVERLOADING BEST PRACTICES ---")

#  DO: Use default arguments for simple overloading
class MathOperations:
    def multiply(self, a, b=1, c=1):
        """Multiply 1, 2, or 3 numbers"""
        return a * b * c

math = MathOperations()
print(f"multiply(5): {math.multiply(5)}")          # 5
print(f"multiply(5, 3): {math.multiply(5, 3)}")    # 15
print(f"multiply(5, 3, 2): {math.multiply(5, 3, 2)}")  # 30

#  DO: Use singledispatch for type-based overloading
@singledispatch
def format_data(data):
    return str(data)

@format_data.register(int)
def _(data):
    return f"Integer: {data:,}"

@format_data.register(float)
def _(data):
    return f"Float: {data:.2f}"

@format_data.register(list)
def _(data):
    return f"List with {len(data)} items"

print(f"format_data(1000000): {format_data(1000000)}")
print(f"format_data(3.14159): {format_data(3.14159)}")
print(f"format_data([1, 2, 3]): {format_data([1, 2, 3])}")

#  DON'T: Use complex manual type checking when simpler options exist
class BadExample:
    def process(self, value, extra=None, extra2=None):
        # This gets messy quickly
        if extra is None and extra2 is None:
            return value
        elif extra2 is None:
            return value + extra
        else:
            return value + extra + extra2

# This works but is less clear than default arguments


# ============================================================
# 2. OVERRIDING BEST PRACTICES
# ============================================================

print("\n--- OVERRIDING BEST PRACTICES ---")

#  DO: Use super() to call parent's method
class Animal:
    def speak(self):
        return "Making a sound"

class Dog(Animal):
    def speak(self):
        # Call parent's version and extend it
        parent_sound = super().speak()
        return f"{parent_sound} - specifically, Woof!"

dog = Dog()
print(f"Dog.speak(): {dog.speak()}")

#  DO: Use the same method signature when overriding
class Bird(Animal):
    def speak(self, loud=False):  # ⚠ Different signature! But it works
        return "Chirp!" if not loud else "CHIRP!"

#  DON'T: Change the method name (that's not overriding, it's a new method)
class Cat(Animal):
    def meow(self):  #  Not overriding - different name
        return "Meow!"

#  DO: Override only when you need different behavior
class Reptile(Animal):
    pass  # No override - inherits the default behavior

#  DO: Override to add additional functionality
class LoudDog(Dog):
    def speak(self):
        parent_sound = super().speak()
        return f"{parent_sound} (very loudly!)"

loud_dog = LoudDog()
print(f"LoudDog.speak(): {loud_dog.speak()}")


# ============================================================
# 3. COMMON MISTAKES TO AVOID
# ============================================================

print("\n--- COMMON MISTAKES ---")

#  MISTAKE 1: Forgetting that Python doesn't support traditional overloading
print("\n MISTAKE 1: Trying to overload with same name")
print("   def add(self, a): return a")
print("   def add(self, a, b): return a + b")
print("   The second one will overwrite the first!")

#  MISTAKE 2: Changing the method signature when overriding
print("\n MISTAKE 2: Changing the method signature")
print("   Parent: def process(self, data): ...")
print("   Child:  def process(self, data, extra): ...")
print("   This can break polymorphism!")

#  MISTAKE 3: Overriding when you don't need to
print("\n MISTAKE 3: Overriding when inheritance would work")
print("   If you're just calling super(), you probably don't need to override")


# ============================================================
# 4. QUICK REFERENCE
# ============================================================

print("\n" + "=" * 60)
print("QUICK REFERENCE")
print("=" * 60)
print("""
┌─────────────────────┬────────────────────────────────────────────────────────────┐
│ CONCEPT             │ RECOMMENDATION                                            │
├─────────────────────┼────────────────────────────────────────────────────────────┤
│ Overloading         │ Use default arguments for most cases                      │
│                     │ Use *args for variable arguments                          │
│                     │ Use singledispatch for type-based overloading             │
│                     │ Avoid complex manual checking if possible                 │
├─────────────────────┼────────────────────────────────────────────────────────────┤
│ Overriding          │ Use super() to call parent methods when needed           │
│                     │ Keep the same method signature                           │
│                     │ Only override when you need different behavior           │
│                     │ Document why you're overriding                           │
├─────────────────────┼────────────────────────────────────────────────────────────┤
│ General             │ Keep it simple - don't overcomplicate                    │
│                     │ Follow Python's "explicit is better than implicit"       │
│                     │ Use type hints for clarity                               │
└─────────────────────┴────────────────────────────────────────────────────────────┘
""")

print(" REMEMBER: Python's design philosophy is 'We're all consenting adults.'")
print("   Use these features wisely and document your intentions clearly.")

Best practices summary:

  • Overloading — use default arguments for simplicity, singledispatch for type-based overloading
  • Overriding — use super() to extend parent behavior, keep the same method signature
  • Don't overcomplicate — Python's philosophy is "simple is better than complex"
  • Document intentions — explain why you're overriding or overloading
  • Avoid changing signatures — when overriding, keep the same parameters

Quick Check: What's the best way to achieve method overloading in Python? (Answer: Default arguments for most cases, singledispatch for type-based overloading)

Try It Yourself

Experiment with overloading and overriding in the editor below.

Loading Pyodide... 0%
Python Code Editor
==================================================
OVERLOADING vs OVERRIDING - PRACTICE
==================================================

1. OVERLOADING WITH DEFAULT ARGUMENTS
add(5): 5
add(5, 3): 8
add(5, 3, 2): 10
multiply(5, 3, 2): 30

2. OVERLOADING WITH singledispatch
describe(10): Integer 10 - doubled: 20
describe('hello'): String 'hello' - uppercase: HELLO
describe([1, 2, 3]): List with 3 items: [1, 2, 3]

3. OVERRIDING

--- SHAPE AREAS ---
This is a Rectangle (width=5, height=3) -> Area: 15
This is a Circle (radius=4) -> Area: 50.26544
This is a Triangle -> Area: 12.0

==================================================
SUMMARY
==================================================
OVERLOADING: Multiple methods with same name in one class
OVERRIDING: Redefining a parent method in a child class
🏆

You've Got It!

You now understand the difference between method overloading and method overriding in Python. You know how to implement both and when to use each approach.

Quick Quiz

Test what you've learned:

1. What is method overloading?
2. Which approach is NOT used for overloading in Python?
3. What is method overriding?
4. Which statement about overriding is TRUE?
5. Why doesn't Python support traditional method overloading?

Frequently Asked Questions

Does Python support method overloading? ▼

Not in the traditional sense. You can't define multiple methods with the same name in the same class. However, you can achieve similar functionality using default arguments, *args, **kwargs, or functools.singledispatch.

Can I overload operators in Python? ▼

Yes! Python supports operator overloading through special methods like __add__, __sub__, __mul__, etc. This is a different concept from method overloading.

What's the difference between overloading and overriding? ▼

Overloading is about having multiple versions of a method in the same class with different parameters. Overriding is about redefining a method in a child class that was inherited from a parent class.

Can I override a method without calling the parent version? ▼

Yes, you can completely replace the parent's implementation. However, it's often a good idea to use super() to extend the parent's behavior rather than replace it entirely, unless you have a good reason to do otherwise.

Is method overriding part of polymorphism? ▼

Yes! Method overriding is a key mechanism for achieving polymorphism in Python. When a child class overrides a parent method, you can call that method on a child object through a parent reference, and the child's version will be executed.

What's the most Pythonic way to achieve overloading? ▼

The most Pythonic approach is to use default arguments for simple cases. For more complex scenarios, use functools.singledispatch for type-based overloading. Avoid complex manual type checking with isinstance() when simpler options exist.

Where to Go From Here

Now that you understand the difference between overloading and overriding, check out these related topics:

Polymorphism in Python

Learn how overloading and overriding enable polymorphic behavior.

Learn More →

Inheritance in Python

Learn more about inheritance, the foundation of overriding.

Learn More →

Method Overriding

Deep dive into method overriding and its use cases.

Learn More →
Interview Resources
  • Python Syntax & Variables Interview Questions
  • Top SQL Interview Questions & Answers
  • SQL Joins: Displaying Data from Multiple Tables FAQ
  • Python Lists and Dictionaries Interview Questions
  • Python OOP Interview Questions
  • SQL Set Operators Interview Questions
Previous: Public, Private and Protected Next: Inheritance vs Composition →