🕯️ Magic Note
The distinction between these method types is one of the unique features of Python. Unlike Java or C++ where everything is an instance method unless declared static, Python gives you explicit tools. The decorators @classmethod, @staticmethod, and @property make your intentions clear to anyone reading your code.
Python
class Dog:
species = “Canis familiaris” # Class attribute
def __init__(self, name, age):
self.name = name
self.age = age
# Instance method
def bark(self):
print(f”{self.name} says: Woof!”)
# Instance method that modifies instance attribute
def celebrate_birthday(self):
self.age += 1
print(f”{self.name} is now {self.age} years old!”)
# Instance method that accesses class attribute
def get_species(self):
return self.species # Works because instance can access class attribute
buddy = Dog(“Buddy”, 3)
buddy.bark() # Buddy says: Woof!
buddy.celebrate_birthday() # Buddy is now 4 years old!
print(buddy.get_species()) # Canis familiaris
Python
class Dog:
total_dogs = 0
def __init__(self, name, age):
self.name = name
self.age = age
Dog.total_dogs += 1
# Class method
@classmethod
def get_total_dogs(cls):
return f”Total dogs created: {cls.total_dogs}”
# Class method as alternative constructor
@classmethod
def from_birth_year(cls, name, birth_year, current_year=2025):
age = current_year – birth_year
return cls(name, age) # Creates and returns a new Dog instance
# Class method to modify class attribute
@classmethod
def reset_count(cls):
cls.total_dogs = 0
return “Dog count reset”
# Called on the class (not on an instance)
print(Dog.get_total_dogs()) # Total dogs created: 0
buddy = Dog(“Buddy”, 3)
max = Dog(“Max”, 5)
print(Dog.get_total_dogs()) # Total dogs created: 2
# Alternative constructor
rocky = Dog.from_birth_year(“Rocky”, 2020)
print(rocky.name, rocky.age) # Rocky 5
print(Dog.get_total_dogs()) # Total dogs created: 3
# Can also be called on an instance (but the instance is ignored)
print(buddy.get_total_dogs()) # Total dogs created: 3 (works but not recommended)
🕯️ Magic Note
Class methods are often used for factory methods (alternative constructors). The built-in datetime.now(), datetime.fromtimestamp(), and datetime.fromisoformat() are class methods of the datetime class.
Python
class MathOperations:
@staticmethod
def add(a, b):
return a + b
@staticmethod
def is_even(number):
return number % 2 == 0
@staticmethod
def factorial(n):
if n <= 1:
return 1
return n * MathOperations.factorial(n – 1)
# Called on the class
print(MathOperations.add(5, 3)) # 8
print(MathOperations.is_even(10)) # True
print(MathOperations.factorial(5)) # 120
# Static methods in a real-world class
class Validator:
@staticmethod
def is_valid_email(email):
return “@” in email and “.” in email.split(“@”)[-1]
@staticmethod
def is_strong_password(password):
return len(password) >= 8 and any(c.isdigit() for c in password)
print(Validator.is_valid_email(“test@example.com”)) # True
print(Validator.is_strong_password(“weak”)) # False
Python
class Circle:
def __init__(self, radius):
self._radius = radius # Internal attribute (convention)
# Getter property
@property
def radius(self):
return self._radius
# Setter property
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError(“Radius cannot be negative”)
self._radius = value
# Computed property (read-only)
@property
def area(self):
return 3.14159 * self._radius ** 2
# Computed property (read-only)
@property
def diameter(self):
return self._radius * 2
c = Circle(5)
print(c.radius) # 5 (accessed like an attribute, not a method)
print(c.area) # 78.53975 (computed automatically)
print(c.diameter) # 10
c.radius = 10 # Uses the setter
print(c.area) # 314.159
# c.radius = -5 # ValueError: Radius cannot be negative
# c.area = 100 # AttributeError: can’t set attribute (read-only)
🕯️ Magic Note
Properties allow you to add logic to attribute access without changing the interface. You can start with a simple attribute and later replace it with a property without breaking code that uses the class. This is called design by contract and is a key principle of encapsulation.
Python
class Temperature:
# Class constant
ABSOLUTE_ZERO_C = -273.15
def __init__(self, celsius=0):
self._celsius = celsius # Internal storage in Celsius
# Instance method (getter property)
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < Temperature.ABSOLUTE_ZERO_C:
raise ValueError(f”Temperature cannot be below absolute zero ({Temperature.ABSOLUTE_ZERO_C}°C)”)
self._celsius = value
# Computed property (Fahrenheit)
@property
def fahrenheit(self):
return (self._celsius * 9/5) + 32
@fahrenheit.setter
def fahrenheit(self, value):
celsius = (value – 32) * 5/9
self.celsius = celsius # Reuse the celsius setter for validation
# Computed property (Kelvin)
@property
def kelvin(self):
return self._celsius + 273.15
@kelvin.setter
def kelvin(self, value):
celsius = value – 273.15
self.celsius = celsius
# Class method: alternative constructor from Fahrenheit
@classmethod
def from_fahrenheit(cls, fahrenheit):
celsius = (fahrenheit – 32) * 5/9
return cls(celsius)
# Class method: alternative constructor from Kelvin
@classmethod
def from_kelvin(cls, kelvin):
celsius = kelvin – 273.15
return cls(celsius)
# Static method: helper function
@staticmethod
def celsius_to_fahrenheit(celsius):
return (celsius * 9/5) + 32
# Instance method
def __str__(self):
return f”{self.celsius:.1f}°C / {self.fahrenheit:.1f}°F / {self.kelvin:.1f}K”
# Using the class
temp = Temperature(25)
print(temp) # 25.0°C / 77.0°F / 298.2K
print(temp.fahrenheit) # 77.0
temp.fahrenheit = 32
print(temp.celsius) # 0.0
temp.kelvin = 300
print(temp.celsius) # 26.85
# Alternative constructors
temp2 = Temperature.from_fahrenheit(212)
print(temp2) # 100.0°C / 212.0°F / 373.2K
temp3 = Temperature.from_kelvin(0)
print(temp3) # -273.2°C / -459.7°F / 0.0K
# Static method
print(Temperature.celsius_to_fahrenheit(100)) # 212.0
| Method Type | First Parameter | Decorator | Access Instance | Access Class | Called On |
|---|---|---|---|---|---|
| Instance | self | None | Yes | Yes | Instance |
| Class | cls | @classmethod | No | Yes | Class (or instance) |
| Static | None | @staticmethod | No (unless passed) | No (unless passed) | Class (or instance) |
| Property | self | @property | Yes | Yes | Instance (like attribute) |
- **Instance Method:** Most methods. Use when you need to access or modify instance-specific data.
- **Class Method:** Alternative constructors, operations that affect the class as a whole, counters, factory patterns.
- **Static Method:** Utility functions, validations, helper functions conceptually related to the class but not needing instance or class state.
- **Property:** Computed attributes, validation on attribute assignment, read-only attributes, backward compatibility.
- Forgetting the @classmethod decorator (then it becomes an instance method expecting self)
- Forgetting the @staticmethod decorator (then it becomes an instance method expecting self, causing errors)
- Calling a class method on an instance (works but is confusing)
- Trying to access instance attributes in a class method without passing an instance
- Creating infinite recursion in property setters (calling self.attribute = value inside the setter instead of self._attribute = value)
Python
# Common mistake: Infinite recursion in property
class Bad:
def __init__(self):
self.value = 0 # This calls the setter! (if property exists)
@property
def value(self):
return self._value
@value.setter
def value(self, v):
self.value = v # Recursion! Calls the setter again!
# Correct way
class Good:
def __init__(self):
self._value = 0 # Direct assignment to internal attribute
@property
def value(self):
return self._value
@value.setter
def value(self, v):
self._value = v # Assign to internal attribute
- What is the difference between an instance method and a class method?
- How do you define a static method? When would you use one?
- What is the purpose of the @property decorator?
- Write a Rectangle class with width and height properties, plus a read-only area property.
- What is an alternative constructor? Give an example.
- Write a class method from_string that creates a Person instance from a string like “Ali,25”
⚡ Whisper
Four types of methods. Four tools in your OOP belt. Instance methods are your daily companions, working with the data of each object. Class methods speak to the blueprint itself, creating new instances or tracking collective information. Static methods are quiet helpers, providing utility without touching object state. Properties are the magicians, turning method calls into attribute access. Each has a role. Each has a place. Use instance methods for behavior that changes per object. Use class methods for factories and counters. Use static methods for utilities. Use properties for computed values and controlled access. Do not force one where another belongs. A static method pretending to be an instance method confuses readers. A property that modifies the world surprises users. Choose intentionally. Your code will sing.