-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
118: Instance Methods, Class Methods, and Static Methods
One of the most common things I see when reviewing code from developers moving into intermediate Python is the "If I don't need self, it's a static method" mentality. It sounds logical, right? If the method doesn't touch any instance-specific data, why bother with the instance? The problem is that this ignores a critical part of how object-oriented programming works: inheritance.
The Myth: "Static methods are just class methods without the cls argument"
Let's look at why this is a trap. Imagine we're building a system for a pizza shop. You want a "factory" method that lets you create a standard Margherita pizza without passing in all the toppings every time.
class Pizza:
def __init__(self, toppings):
self.toppings = toppings
@staticmethod
def create_margherita():
# We hardcode 'Pizza' here because it's a static method
return Pizza(["mozzarella", "basil", "tomato"])
class DeepDishPizza(Pizza):
pass
# This works as expected
standard = Pizza.create_margherita()
# This is where the wheels fall off
deep_dish = DeepDishPizza.create_margherita()
print(type(deep_dish)) # Output: <class '__main__.Pizza'>
Notice the problem? I called create_margherita() on the DeepDishPizza class, but I got back a base Pizza object. Because a static method knows nothing about the class it was called on, it's forced to hardcode the class name. This completely breaks polymorphism. If you're building a library or a framework, this is a nightmare for anyone trying to extend your code.
The Fix: Using @classmethod for polymorphic factories
This is where @classmethod earns its keep. Unlike a static method, a class method receives a reference to the class itself as the first argument (which we conventionally call cls). It doesn't care which specific class it is; it just knows it's the one currently being used.
class Pizza: def __init__(self, toppings): self.toppings = toppings @classmethod def create_margherita(cls): # cls refers to whatever class called this method return cls(["mozzarella", "basil", "tomato"]) class DeepDishPizza(Pizza): pass deep_dish = DeepDishPizza.create_margherita() print(type(deep_dish)) # Output: <class '__main__.DeepDishPizza'>By using
cls(...)instead ofPizza(...), the method becomes dynamic. Now, whenDeepDishPizzacalls the method,clsisDeepDishPizza. This is the gold standard for creating "alternative constructors" in Python.Reserving
@staticmethodfor pure utility logicSo, does
@staticmethodeven have a purpose? Yes, but it's much narrower. Use a static method when the function logically belongs inside the class (for organization and namespaces), but it doesn't need to know anything about the class or the instance.I usually use them for validation or helper functions. For example, checking if a topping is "legal" in our shop doesn't require knowing about a specific pizza order, nor does it require knowing about the
Pizzaclass hierarchy.class Pizza: # ... (previous code) ... @staticmethod def is_valid_topping(topping): # This logic is independent of class state or instance state allowed = ["mozzarella", "basil", "tomato", "pepperoni", "mushrooms"] return topping.lower() in allowed # I can call this without even creating a Pizza object if Pizza.is_valid_topping("pineapple"): print("Adding pineapple!") else: print("We don't do that here.")To wrap this up, here is my personal rule of thumb:
- Instance Method: Use it if you need to read or change the state of a specific object (use
self). - Class Method: Use it for factory methods or when you need to touch class-level state (use
cls). - Static Method: Use it for utility functions that are related to the class but are essentially "standalone" functions.
📋 Practical Task
Implement a Flexible Employee Management System
You are building an HR system. You need to create an Employee class that can be instantiated in two ways: via a standard constructor (name and salary) and via a class method that parses a string (e.g., "John Doe, 50000").
Requirements:
- Create an
Employeeclass with an__init__method acceptingnameandsalary. - Implement a
@classmethodcalledfrom_stringthat takes a single string argument in the format"Name, Salary", splits it, and returns a new instance of the class. - Implement a
@staticmethodcalledis_valid_salarythat takes a number and returnsTrueif the salary is greater than 0, andFalseotherwise. - Create a subclass called
Manager. - Demonstrate that calling
Manager.from_string("Jane Smith, 80000")correctly returns aManagerobject, not a baseEmployeeobject.
There are no comments for now.