Popular Searches
Popular Course Categories
Popular Courses

Using Class Methods, Static Methods, and Decorators

Using Class Methods, Static Methods, and Decorators

Advanced Object-Oriented Python Concepts

Using Class Methods, Static Methods, and Decorators in Python

Course: Python Training

Topic: Using Class Methods, Static Methods, and Decorators

Course Link: Python Training Course at JustAcademy

Demo Link: Register for a Free Course Demo

Website: JustAcademy


1. Introduction

Python provides different types of methods to organize code inside classes. Instance methods, class methods, and static methods serve different purposes depending on whether a method needs access to an individual object, the class itself, or neither.

Python also supports decorators, which allow developers to modify or extend the behavior of functions and methods without changing their original implementation. Understanding these concepts is essential for writing reusable, maintainable, and well-structured object-oriented Python applications.

Class methods, static methods, and decorators are commonly used in applications involving object creation, configuration management, validation, logging, utility operations, and reusable business logic.

2. Learning Objectives

  • Understand the purpose of instance methods, class methods, and static methods.
  • Learn how the @classmethod decorator works.
  • Understand the role of the @staticmethod decorator.
  • Learn how decorators modify function behavior.
  • Understand the difference between self and cls.
  • Explore method decorators, custom decorators, and decorator chaining.
  • Apply these concepts through practical Python examples.

3. Understanding Methods in Python

A method is a function defined inside a class. Methods help describe the behavior of objects and organize related operations into a logical structure.

Python commonly uses three method types:

Method Type First Parameter Access to Instance Access to Class Typical Purpose
Instance method self Yes Through the instance Working with individual object data
Class method cls No automatic instance reference Yes Working with class-level data or creating objects
Static method No automatic first parameter No No automatic class reference Utility operations related to a class

4. Understanding Instance Methods

An instance method operates on a particular object. Its first parameter is conventionally named self, which refers to the current instance.

Instance methods can access and modify instance attributes and can also access class attributes through the instance or class.

Example: Instance Method

class Student:
    def __init__(self, name, marks):
        self.name = name
        self.marks = marks

    def display_details(self):
        print("Name:", self.name)
        print("Marks:", self.marks)

    def update_marks(self, new_marks):
        self.marks = new_marks

student = Student("Amit", 85)
student.display_details()

student.update_marks(92)
student.display_details()

Output

Name: Amit
Marks: 85
Name: Amit
Marks: 92

Explanation

  • __init__() initializes the student's attributes.
  • display_details() reads information from the current object.
  • update_marks() modifies the current object's marks.
  • The self parameter allows each student object to maintain its own data.

5. Understanding Class Methods

A class method is a method associated with the class rather than a particular instance. It is defined using the @classmethod decorator, and Python automatically passes the class as the first argument, conventionally named cls.

Class methods are useful when an operation needs to read or modify class-level attributes or create instances using alternative construction logic.

Syntax

class ClassName:
    class_attribute = "value"

    @classmethod
    def method_name(cls):
        return cls.class_attribute

Example: Accessing Class Variables

class Employee:
    company = "Tech Solutions"
    employee_count = 0

    def __init__(self, name):
        self.name = name
        Employee.employee_count += 1

    @classmethod
    def display_company(cls):
        print("Company:", cls.company)
        print("Employees:", cls.employee_count)

employee1 = Employee("Ravi")
employee2 = Employee("Priya")

Employee.display_company()

Output

Company: Tech Solutions
Employees: 2

Explanation

  • company and employee_count are class attributes.
  • The @classmethod decorator identifies display_company() as a class method.
  • The cls parameter refers to the class on which the method is called.
  • The method can be called using Employee.display_company() without creating another object.

Important: In this example, the constructor updates Employee.employee_count directly. In inheritance-aware code, use type(self).employee_count or a carefully designed class method when subclass-specific counting is required.

6. Using Class Methods to Create Objects

Class methods are frequently used as alternative constructors. An alternative constructor provides another way to create an object when the input data is supplied in a different format.

Example: Alternative Constructor

class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price

    @classmethod
    def from_string(cls, product_data):
        name, price = product_data.split(",")
        return cls(name.strip(), float(price.strip()))

product = Product.from_string("Laptop, 55000")

print(product.name)
print(product.price)

Output

Laptop
55000.0

Explanation

  • from_string() accepts product information as a single string.
  • The method splits the string into a name and price.
  • cls(...) creates an object using the class passed to the method.
  • This pattern can support subclasses because cls refers to the class used to call the method.

When to Use Alternative Constructors

  • Creating objects from CSV rows.
  • Loading configuration values from strings.
  • Building objects from dictionaries or API responses.
  • Creating objects from database records.
  • Supporting multiple input formats for the same class.

7. Understanding Static Methods

A static method is a function placed inside a class for organizational convenience. It is defined using the @staticmethod decorator.

Unlike instance and class methods, a static method does not automatically receive self or cls. It behaves like a regular function while remaining accessible through the class or its instances.

Syntax

class ClassName:
    @staticmethod
    def utility_method(value):
        return value

Example: Static Method for Validation

class NumberUtility:
    @staticmethod
    def is_even(number):
        return number % 2 == 0

print(NumberUtility.is_even(10))
print(NumberUtility.is_even(7))

Output

True
False

Explanation

  • is_even() checks whether a number is divisible by two.
  • The method does not need instance attributes.
  • It does not need to access class attributes.
  • The @staticmethod decorator makes its intended role clear.

8. Practical Example of a Static Method

Consider an online shopping application that needs to calculate a discount percentage. The calculation depends only on the values passed to the method.

class ShoppingCart:
    @staticmethod
    def calculate_discount(price, discount_percent):
        if price < 0:
            raise ValueError("Price cannot be negative")

        if not 0 <= discount_percent <= 100:
            raise ValueError("Discount must be between 0 and 100")

        return price * discount_percent / 100

discount = ShoppingCart.calculate_discount(2000, 15)
print("Discount:", discount)
print("Final price:", 2000 - discount)

Output

Discount: 300.0
Final price: 1700.0

This method is suitable as a static method because the calculation does not depend on a particular shopping cart or class-level state.

9. Difference Between Class Methods and Static Methods

Feature Class Method Static Method
Decorator @classmethod @staticmethod
First argument cls No automatic argument
Receives class automatically Yes No
Receives instance automatically No No
Can access class attributes Directly through cls Only if the class or its data is explicitly supplied or otherwise referenced
Common use Alternative constructors and class-level operations Validation, conversion, and utility calculations

10. Understanding Decorators in Python

A decorator is a callable that takes another function or callable and returns a function or callable, often with additional behavior. Decorators allow developers to extend or modify behavior without rewriting the original function's main logic.

Decorators are widely used for logging, authorization, timing, caching, validation, registration, and method definitions such as @classmethod, @staticmethod, and @property.

Basic Decorator Syntax

def decorator_function(original_function):
    def wrapper():
        print("Before function execution")
        original_function()
        print("After function execution")
    return wrapper

@decorator_function
def greet():
    print("Hello, Python!")

greet()

Output

Before function execution
Hello, Python!
After function execution

How It Works

  1. The function greet() is defined.
  2. The decorator receives the original function.
  3. The decorator returns the wrapper function.
  4. When greet() is called, the wrapper runs the additional statements and calls the original function.

The syntax @decorator_function is equivalent to assigning the decorated result back to the original name, as in greet = decorator_function(greet).

11. Creating Decorators That Accept Arguments

In real applications, decorated functions often accept parameters. A reusable decorator should accept arbitrary positional and keyword arguments using *args and **kwargs.

from functools import wraps

def log_call(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("Calling:", func.__name__)
        result = func(*args, **kwargs)
        print("Function completed")
        return result
    return wrapper

@log_call
def add(a, b):
    return a + b

result = add(10, 20)
print("Result:", result)

Output

Calling: add
Function completed
Result: 30

Explanation

  • *args accepts positional arguments.
  • **kwargs accepts keyword arguments.
  • func(*args, **kwargs) forwards the supplied arguments to the original function.
  • return result preserves the original function's return value.
  • @wraps(func) preserves useful metadata such as the original function's name and docstring.

12. Using Decorators for Execution Time

Decorators can measure how long a function takes to execute. This is useful when profiling application performance or identifying slow operations.

import time
from functools import wraps

def measure_time(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        try:
            return func(*args, **kwargs)
        finally:
            elapsed = time.perf_counter() - start
            print(f"Execution time: {elapsed:.6f} seconds")
    return wrapper

@measure_time
def calculate_sum():
    return sum(range(100000))

result = calculate_sum()
print("Calculation completed:", result)

Explanation

  • time.perf_counter() provides a high-resolution timer suitable for measuring elapsed time.
  • The decorator measures the duration of the function call.
  • The finally block reports elapsed time even if the decorated function raises an exception.
  • The wrapper returns the original result when the function completes successfully.

Note: The measured duration varies depending on the computer, system load, and execution environment.

13. Decorators With Their Own Arguments

A parameterized decorator allows you to configure the behavior of the decorator itself. This typically requires three nested functions: one to receive the decorator arguments, one to receive the original function, and one to execute the wrapped function.

from functools import wraps

def repeat(times):
    if times < 0:
        raise ValueError("times cannot be negative")

    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            result = None
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def say_hello():
    print("Hello!")

say_hello()

Output

Hello!
Hello!
Hello!

Explanation

  • repeat(3) configures the decorator to execute the function three times.
  • decorator() receives the function being decorated.
  • wrapper() controls each execution.
  • The final result is the result of the last execution, or None when the function is not executed.

14. Applying Multiple Decorators

Python allows multiple decorators to be applied to the same function. They are applied from the closest decorator to the function outward.

from functools import wraps

def bold(func):
    @wraps(func)
    def wrapper():
        return "<b>" + func() + "</b>"
    return wrapper

def italic(func):
    @wraps(func)
    def wrapper():
        return "<i>" + func() + "</i>"
    return wrapper

@bold
@italic
def message():
    return "Hello Python"

print(message())

Output

<b><i>Hello Python</i></b>

Explanation

The decorator stack is equivalent to message = bold(italic(message)). The italic decorator wraps the original function first, and bold wraps that result.

15. Built-in Decorators in Python

Python includes several built-in decorators that support object-oriented programming and other common development tasks.

Decorator Purpose Example Use
@classmethod Creates a method receiving the class as its first argument. Alternative constructors
@staticmethod Defines a method without automatic instance or class arguments. Utility calculations
@property Allows a method to be accessed like an attribute. Computed values and controlled attribute access
@functools.wraps Preserves metadata when writing wrappers. Custom decorators
@functools.lru_cache Caches results of calls based on their arguments. Repeated, expensive calculations
@dataclasses.dataclass Generates common special methods for data-focused classes. Data models

16. Using the @property Decorator With Methods

The @property decorator turns a method into an attribute-like interface. It is useful for calculating values or validating updates without requiring callers to use explicit getter and setter method names.

class Rectangle:
    def __init__(self, length, width):
        self.length = length
        self.width = width

    @property
    def area(self):
        return self.length * self.width

rectangle = Rectangle(10, 5)
print(rectangle.area)

Output

50

Notice that rectangle.area is accessed without parentheses. The property provides a clean interface while performing the calculation when accessed.

17. Choosing the Right Method Type

Choosing the appropriate method type makes class design easier to understand and maintain.

  • Use an instance method when the operation depends on a particular object's data.
  • Use a class method when the operation needs the class itself or should create an instance using cls.
  • Use a static method when the operation logically belongs to the class but does not need automatic access to the instance or class.
  • Use a custom decorator when additional behavior should be applied consistently to one or more functions or methods.

Example Combining All Three Method Types

class Temperature:
    unit = "Celsius"

    def __init__(self, value):
        self.value = value

    def display(self):
        return f"{self.value} {type(self).unit}"

    @classmethod
    def from_fahrenheit(cls, value):
        celsius = (value - 32) * 5 / 9
        return cls(celsius)

    @staticmethod
    def is_valid(value):
        return isinstance(value, (int, float))

temperature = Temperature(25)
print(temperature.display())

converted = Temperature.from_fahrenheit(98.6)
print(round(converted.value, 1))

print(Temperature.is_valid(30))

Output

25 Celsius
37.0
True

18. Common Mistakes and Troubleshooting

Mistake Why It Happens Solution
Using self in a class method Class methods do not automatically receive an instance. Use cls or call an instance method on an object.
Using cls in a static method without defining it Static methods receive no automatic class argument. Pass the class explicitly or use a class method.
Forgetting to return the wrapper The decorated function may become None. Return the wrapper from the decorator.
Not forwarding arguments The wrapper may fail when called with parameters. Use *args and **kwargs when appropriate.
Dropping the return value The wrapper may discard the original function's result. Return the result of calling the wrapped function.
Skipping @wraps Function metadata may describe the wrapper instead of the original function. Use functools.wraps in custom decorators.
Choosing a static method unnecessarily The method may actually need class or instance state. Choose the method type based on the data it needs.

19. Best Practices

  • Choose meaningful method names that explain their purpose.
  • Use cls consistently as the first parameter of class methods.
  • Keep static methods focused on a single utility task.
  • Use class methods for alternative constructors that should respect inheritance.
  • Use functools.wraps when implementing decorators that wrap functions.
  • Preserve function arguments, return values, and exceptions unless intentionally changing the behavior.
  • Keep decorators small and reusable.
  • Document decorators that change function execution, caching, permissions, or error handling.
  • Write unit tests for methods and decorators, including edge cases and invalid inputs.
  • Avoid unnecessary decorators when a normal function or method would be clearer.

20. Practical Exercises

Exercise 1: Class Method

Create a BankAccount class with a class attribute called bank_name. Define a class method that displays the bank name.

Expected learning: Accessing class-level attributes using cls.

Exercise 2: Alternative Constructor

Create a Book class with title and author attributes. Implement a class method that creates a book from a string formatted as "Title, Author".

Expected learning: Using a class method to parse data and construct objects.

Exercise 3: Static Method

Create a MathHelper class with a static method that checks whether a number is prime.

Expected learning: Writing a utility method that does not depend on object state.

Exercise 4: Logging Decorator

Create a decorator that prints the name of a function before executing it. Apply the decorator to two different functions.

Expected learning: Reusing decorators across multiple functions.

Exercise 5: Retry Decorator

Write a parameterized decorator that retries a function a specified number of times when it raises an exception. Re-raise the final exception if all attempts fail.

Expected learning: Handling exceptions and controlling repeated function execution.

21. Mini Project: Employee Management System

Develop a small employee management application using instance methods, class methods, static methods, and a custom decorator.

Project Requirements

  • Create an Employee class with employee ID, name, and salary.
  • Use an instance method to display employee details.
  • Use a class method to create an employee from a string.
  • Use a static method to validate whether a salary is a positive number.
  • Create a decorator that logs when employee information is displayed.
  • Test the program with multiple employee objects.

Sample Implementation

from functools import wraps

def log_action(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("Action:", func.__name__)
        return func(*args, **kwargs)
    return wrapper

class Employee:
    company = "ABC Technologies"

    def __init__(self, employee_id, name, salary):
        if not self.is_valid_salary(salary):
            raise ValueError("Salary must be a positive number")

        self.employee_id = employee_id
        self.name = name
        self.salary = salary

    @classmethod
    def from_string(cls, data):
        employee_id, name, salary = data.split(",")
        return cls(
            employee_id.strip(),
            name.strip(),
            float(salary.strip())
        )

    @staticmethod
    def is_valid_salary(salary):
        return (
            isinstance(salary, (int, float))
            and not isinstance(salary, bool)
            and salary > 0
        )

    @log_action
    def display_details(self):
        print("Company:", type(self).company)
        print("Employee ID:", self.employee_id)
        print("Name:", self.name)
        print("Salary:", self.salary)

employee = Employee.from_string("E101, Neha, 45000")
employee.display_details()

Sample Output

Action: display_details
Company: ABC Technologies
Employee ID: E101
Name: Neha
Salary: 45000.0

Project Concepts Demonstrated

  • Instance methods manage individual employee information.
  • The class method creates an employee from a string.
  • The static method validates salary input.
  • The custom decorator adds logging behavior to a method.
  • Exception handling prevents the creation of employees with invalid salaries.

22. Interview Questions and Answers

Q1. What is a class method in Python?

A class method is a method decorated with @classmethod that receives the class as its first argument, conventionally named cls.

Q2. What is the purpose of the @staticmethod decorator?

It defines a method that does not automatically receive an instance or class argument. It is useful for utility functions logically associated with a class.

Q3. What is the difference between self and cls?

self conventionally refers to an individual instance, while cls refers to the class on which a class method is called.

Q4. Can a class method access instance variables?

Not directly through an automatically supplied instance, because a class method receives the class rather than an instance. It can access instance data if an instance is explicitly passed to it.

Q5. Can a static method access class variables?

A static method has no automatic cls parameter, but it can access class variables if the class is referenced explicitly or passed as an argument.

Q6. What is a decorator?

A decorator is a callable that takes another callable and returns a callable, often adding behavior around the original operation.

Q7. What does @classmethod do internally?

It creates a class method descriptor. When accessed through a class or instance, Python binds the method to the appropriate class and supplies that class as the first argument.

Q8. What does @staticmethod do?

It creates a static method descriptor so that accessing the method through a class or instance does not automatically bind an instance or class argument.

Q9. Why is functools.wraps used?

It preserves important metadata from the original function, including its name, documentation string, and wrapped-function reference.

Q10. What is a parameterized decorator?

It is a decorator configured with arguments, such as a repeat count or a logging option, before it receives the function being decorated.

Q11. Can decorators be applied to class methods?

Yes. Custom decorators can wrap class methods, but the order of decorators matters. A common pattern is to apply a custom function decorator above @classmethod, so it receives the bound method appropriately.

Q12. When should you use a class method instead of a static method?

Use a class method when the implementation needs the class itself, particularly for class-level operations and alternative constructors. Use a static method when no automatic class or instance reference is needed.

23. Quick Revision Checklist

  • Instance method: Uses self to work with an instance.
  • Class method: Uses @classmethod and receives cls.
  • Static method: Uses @staticmethod without an automatic first argument.
  • Decorator: Extends or modifies the behavior of a callable.
  • Wrapper: A function that adds behavior around another function.
  • @wraps: Preserves metadata from the original function.
  • Alternative constructor: A class method that creates an instance using another input format.
  • Best practice: Select the method type according to the data and behavior required.

24. Conclusion

Class methods, static methods, and decorators are important features of Python that help developers build reusable and maintainable applications. Instance methods operate on individual objects, class methods operate with class-level context, and static methods provide related utilities without automatic instance or class binding.

Decorators extend this flexibility by adding reusable behavior such as logging, timing, validation, and execution control. By practicing these concepts through small programs and projects, learners can strengthen their understanding of Python object-oriented programming and write clearer, more professional code.

To continue learning Python programming, explore the Python Training Course at JustAcademy, visit the JustAcademy website, or register for a course demo.

whatsapp