OOP Advanced
@classmethod and @staticmethod
Class-Level Methods
You want User.from_json(data) to create a User from JSON. Regular methods get
self (instance). @classmethod gets cls (class) - perfect for factory methods.
@staticmethod gets neither - just a function in the class namespace.
The three method types
Regular, classmethod, and staticmethod.
# Understanding the Three Method Types
class Counter:
"""Demonstrates all three method types."""
count = 0 # Class attribute - shared by all instances
def __init__(self, name: str):
self.name = name # Instance attribute
Counter.count += 1
# Regular method - receives self (instance)
def display(self):
print(f"Instance '{self.name}' (total count: {Counter.count})")
# Class method - receives cls (class)
@classmethod
def get_count(cls):
print(f"Class: {cls.__name__}")
print(f"Total instances: {cls.count}")
return cls.count
# Static method - receives nothing
@staticmethod
def describe():
print("Counter tracks how many instances exist")
def main():
print("=== Three Method Types ===\n")
# Static method - no instance needed
print("--- Static Method (no instance needed) ---")
Counter.describe() # Call on class
# Class method - no instance needed
print("\n--- Class Method (before any instances) ---")
Counter.get_count() # 0 instances
# Create instances
print("\n--- Creating Instances ---")
c1 = Counter("first")
c2 = Counter("second")
c3 = Counter("third")
# Regular method - needs instance
print("\n--- Regular Method (needs instance) ---")
c1.display() # Instance method
c2.display()
# Class method - after creating instances
print("\n--- Class Method (after instances) ---")
Counter.get_count() # 3 instances
# Can also call class method on instance
print("\n--- Class Method Called on Instance ---")
c1.get_count() # Still gets class
# Can also call static method on instance
print("\n--- Static Method Called on Instance ---")
c1.describe() # Works but not common
# Summary
print("\n=== Key Points ===")
print("""
Regular method:
def method(self): → receives instance
Call: instance.method()
Class method:
@classmethod
def method(cls): → receives class
Call: Class.method() or instance.method()
Static method:
@staticmethod
def method(): → receives nothing
Call: Class.method() or instance.method()
""")
if __name__ == "__main__":
main()
Regular: self. Classmethod: cls. Staticmethod: neither.
Factory methods with @classmethod
Alternative constructors that return instances.
# Using @classmethod for Factory Methods
class Product:
"""Product with factory methods."""
def __init__(self, name: str, price: float, category: str):
self.name = name
self.price = price
self.category = category
def __repr__(self):
return f"Product({self.name!r}, ${self.price:.2f}, {self.category})"
# Factory method: create from dictionary
@classmethod
def from_dict(cls, data: dict):
"""Create Product from a dictionary."""
return cls(
name=data["name"],
price=data["price"],
category=data.get("category", "general")
)
# Factory method: create from string
@classmethod
def from_string(cls, s: str):
"""Create Product from 'name:price:category' string."""
parts = s.split(":")
name = parts[0]
price = float(parts[1])
category = parts[2] if len(parts) > 2 else "general"
return cls(name, price, category)
# Factory method: create with defaults
@classmethod
def create_digital(cls, name: str, price: float):
"""Create a digital product."""
return cls(name, price, "digital")
@classmethod
def create_physical(cls, name: str, price: float):
"""Create a physical product."""
return cls(name, price, "physical")
class User:
"""User with factory methods for different user types."""
def __init__(self, username: str, email: str, role: str, is_active: bool = True):
self.username = username
self.email = email
self.role = role
self.is_active = is_active
def __repr__(self):
status = "active" if self.is_active else "inactive"
return f"User({self.username}, {self.role}, {status})"
@classmethod
def create_admin(cls, username: str, email: str):
"""Factory method for admin users."""
return cls(username, email, role="admin")
@classmethod
def create_guest(cls):
"""Factory method for guest users."""
return cls("guest", "guest@example.com", role="guest", is_active=False)
@classmethod
def from_dict(cls, data: dict):
"""Create User from dictionary."""
return cls(**data) # Unpack dict as keyword args
def main():
print("=== Factory Methods with @classmethod ===\n")
# Standard constructor
print("--- Standard Constructor ---")
p1 = Product("Laptop", 999.99, "electronics")
print(f"Standard: {p1}")
# Factory: from dictionary
print("\n--- Factory: from_dict ---")
data = {"name": "Mouse", "price": 29.99, "category": "electronics"}
p2 = Product.from_dict(data)
print(f"From dict: {p2}")
# Factory: from string
print("\n--- Factory: from_string ---")
p3 =
print(f"From string: {p3}")
p4 = Product.from_string("Sticker:2.99")
print(f"From string (default category): {p4}")
# Factory: preset categories
print("\n--- Factory: Preset Categories ---")
p5 = Product.create_digital("E-book", 14.99)
p6 = Product.create_physical("T-shirt", 24.99)
print(f"Digital: {p5}")
print(f"Physical: {p6}")
# User factory methods
print("\n--- User Factory Methods ---")
admin = User.create_admin("admin", "admin@example.com")
guest = User.create_guest()
user_data =
regular = User.from_dict(user_data)
print(f"Admin: {admin}")
print(f"Guest: {guest}")
print(f"Regular: {regular}")
print("\n=== Key Points ===")
print("""
Factory methods (@classmethod):
• Return new instances of the class
• Use cls() instead of ClassName()
• Provide alternative ways to create objects
• Can have descriptive names like from_dict, create_admin
• Handle data conversion/validation
""")
if __name__ == "__main__":
main()
# Using @classmethod for Factory Methods
class Product:
"""Product with factory methods."""
def __init__(self, name: str, price: float, category: str):
self.name = name
self.price = price
self.category = category
def __repr__(self):
return f"Product({self.name!r}, ${self.price:.2f}, {self.category})"
# Factory method: create from dictionary
@classmethod
def from_dict(cls, data: dict):
"""Create Product from a dictionary."""
return cls(
name=data["name"],
price=data["price"],
category=data.get("category", "general")
)
# Factory method: create from string
@classmethod
def from_string(cls, s: str):
"""Create Product from 'name:price:category' string."""
parts = s.split(":")
name = parts[0]
price = float(parts[1])
category = parts[2] if len(parts) > 2 else "general"
return cls(name, price, category)
# Factory method: create with defaults
@classmethod
def create_digital(cls, name: str, price: float):
"""Create a digital product."""
return cls(name, price, "digital")
@classmethod
def create_physical(cls, name: str, price: float):
"""Create a physical product."""
return cls(name, price, "physical")
class User:
"""User with factory methods for different user types."""
def __init__(self, username: str, email: str, role: str, is_active: bool = True):
self.username = username
self.email = email
self.role = role
self.is_active = is_active
def __repr__(self):
status = "active" if self.is_active else "inactive"
return f"User({self.username}, {self.role}, {status})"
@classmethod
def create_admin(cls, username: str, email: str):
"""Factory method for admin users."""
return cls(username, email, role="admin")
@classmethod
def create_guest(cls):
"""Factory method for guest users."""
return cls("guest", "guest@example.com", role="guest", is_active=False)
@classmethod
def from_dict(cls, data: dict):
"""Create User from dictionary."""
return cls(**data) # Unpack dict as keyword args
def main():
print("=== Factory Methods with @classmethod ===\n")
# Standard constructor
print("--- Standard Constructor ---")
p1 = Product("Laptop", 999.99, "electronics")
print(f"Standard: {p1}")
# Factory: from dictionary
print("\n--- Factory: from_dict ---")
data = {"name": "Mouse", "price": 29.99, "category": "electronics"}
p2 = Product.from_dict(data)
print(f"From dict: {p2}")
# Factory: from string
print("\n--- Factory: from_string ---")
p3 =
print(f"From string: {p3}")
p4 = Product.from_string("Sticker:2.99")
print(f"From string (default category): {p4}")
# Factory: preset categories
print("\n--- Factory: Preset Categories ---")
p5 = Product.create_digital("E-book", 14.99)
p6 = Product.create_physical("T-shirt", 24.99)
print(f"Digital: {p5}")
print(f"Physical: {p6}")
# User factory methods
print("\n--- User Factory Methods ---")
admin = User.create_admin("admin", "admin@example.com")
guest = User.create_guest()
user_data =
regular = User.from_dict(user_data)
print(f"Admin: {admin}")
print(f"Guest: {guest}")
print(f"Regular: {regular}")
print("\n=== Key Points ===")
print("""
Factory methods (@classmethod):
• Return new instances of the class
• Use cls() instead of ClassName()
• Provide alternative ways to create objects
• Can have descriptive names like from_dict, create_admin
• Handle data conversion/validation
""")
if __name__ == "__main__":
main()
# Using @classmethod for Factory Methods
class Product:
"""Product with factory methods."""
def __init__(self, name: str, price: float, category: str):
self.name = name
self.price = price
self.category = category
def __repr__(self):
return f"Product({self.name!r}, ${self.price:.2f}, {self.category})"
# Factory method: create from dictionary
@classmethod
def from_dict(cls, data: dict):
"""Create Product from a dictionary."""
return cls(
name=data["name"],
price=data["price"],
category=data.get("category", "general")
)
# Factory method: create from string
@classmethod
def from_string(cls, s: str):
"""Create Product from 'name:price:category' string."""
parts = s.split(":")
name = parts[0]
price = float(parts[1])
category = parts[2] if len(parts) > 2 else "general"
return cls(name, price, category)
# Factory method: create with defaults
@classmethod
def create_digital(cls, name: str, price: float):
"""Create a digital product."""
return cls(name, price, "digital")
@classmethod
def create_physical(cls, name: str, price: float):
"""Create a physical product."""
return cls(name, price, "physical")
class User:
"""User with factory methods for different user types."""
def __init__(self, username: str, email: str, role: str, is_active: bool = True):
self.username = username
self.email = email
self.role = role
self.is_active = is_active
def __repr__(self):
status = "active" if self.is_active else "inactive"
return f"User({self.username}, {self.role}, {status})"
@classmethod
def create_admin(cls, username: str, email: str):
"""Factory method for admin users."""
return cls(username, email, role="admin")
@classmethod
def create_guest(cls):
"""Factory method for guest users."""
return cls("guest", "guest@example.com", role="guest", is_active=False)
@classmethod
def from_dict(cls, data: dict):
"""Create User from dictionary."""
return cls(**data) # Unpack dict as keyword args
def main():
print("=== Factory Methods with @classmethod ===\n")
# Standard constructor
print("--- Standard Constructor ---")
p1 = Product("Laptop", 999.99, "electronics")
print(f"Standard: {p1}")
# Factory: from dictionary
print("\n--- Factory: from_dict ---")
data = {"name": "Mouse", "price": 29.99, "category": "electronics"}
p2 = Product.from_dict(data)
print(f"From dict: {p2}")
# Factory: from string
print("\n--- Factory: from_string ---")
p3 =
print(f"From string: {p3}")
p4 = Product.from_string("Sticker:2.99")
print(f"From string (default category): {p4}")
# Factory: preset categories
print("\n--- Factory: Preset Categories ---")
p5 = Product.create_digital("E-book", 14.99)
p6 = Product.create_physical("T-shirt", 24.99)
print(f"Digital: {p5}")
print(f"Physical: {p6}")
# User factory methods
print("\n--- User Factory Methods ---")
admin = User.create_admin("admin", "admin@example.com")
guest = User.create_guest()
user_data =
regular = User.from_dict(user_data)
print(f"Admin: {admin}")
print(f"Guest: {guest}")
print(f"Regular: {regular}")
print("\n=== Key Points ===")
print("""
Factory methods (@classmethod):
• Return new instances of the class
• Use cls() instead of ClassName()
• Provide alternative ways to create objects
• Can have descriptive names like from_dict, create_admin
• Handle data conversion/validation
""")
if __name__ == "__main__":
main()
# Using @classmethod for Factory Methods
class Product:
"""Product with factory methods."""
def __init__(self, name: str, price: float, category: str):
self.name = name
self.price = price
self.category = category
def __repr__(self):
return f"Product({self.name!r}, ${self.price:.2f}, {self.category})"
# Factory method: create from dictionary
@classmethod
def from_dict(cls, data: dict):
"""Create Product from a dictionary."""
return cls(
name=data["name"],
price=data["price"],
category=data.get("category", "general")
)
# Factory method: create from string
@classmethod
def from_string(cls, s: str):
"""Create Product from 'name:price:category' string."""
parts = s.split(":")
name = parts[0]
price = float(parts[1])
category = parts[2] if len(parts) > 2 else "general"
return cls(name, price, category)
# Factory method: create with defaults
@classmethod
def create_digital(cls, name: str, price: float):
"""Create a digital product."""
return cls(name, price, "digital")
@classmethod
def create_physical(cls, name: str, price: float):
"""Create a physical product."""
return cls(name, price, "physical")
class User:
"""User with factory methods for different user types."""
def __init__(self, username: str, email: str, role: str, is_active: bool = True):
self.username = username
self.email = email
self.role = role
self.is_active = is_active
def __repr__(self):
status = "active" if self.is_active else "inactive"
return f"User({self.username}, {self.role}, {status})"
@classmethod
def create_admin(cls, username: str, email: str):
"""Factory method for admin users."""
return cls(username, email, role="admin")
@classmethod
def create_guest(cls):
"""Factory method for guest users."""
return cls("guest", "guest@example.com", role="guest", is_active=False)
@classmethod
def from_dict(cls, data: dict):
"""Create User from dictionary."""
return cls(**data) # Unpack dict as keyword args
def main():
print("=== Factory Methods with @classmethod ===\n")
# Standard constructor
print("--- Standard Constructor ---")
p1 = Product("Laptop", 999.99, "electronics")
print(f"Standard: {p1}")
# Factory: from dictionary
print("\n--- Factory: from_dict ---")
data = {"name": "Mouse", "price": 29.99, "category": "electronics"}
p2 = Product.from_dict(data)
print(f"From dict: {p2}")
# Factory: from string
print("\n--- Factory: from_string ---")
p3 =
print(f"From string: {p3}")
p4 = Product.from_string("Sticker:2.99")
print(f"From string (default category): {p4}")
# Factory: preset categories
print("\n--- Factory: Preset Categories ---")
p5 = Product.create_digital("E-book", 14.99)
p6 = Product.create_physical("T-shirt", 24.99)
print(f"Digital: {p5}")
print(f"Physical: {p6}")
# User factory methods
print("\n--- User Factory Methods ---")
admin = User.create_admin("admin", "admin@example.com")
guest = User.create_guest()
user_data =
regular = User.from_dict(user_data)
print(f"Admin: {admin}")
print(f"Guest: {guest}")
print(f"Regular: {regular}")
print("\n=== Key Points ===")
print("""
Factory methods (@classmethod):
• Return new instances of the class
• Use cls() instead of ClassName()
• Provide alternative ways to create objects
• Can have descriptive names like from_dict, create_admin
• Handle data conversion/validation
""")
if __name__ == "__main__":
main()
# Using @classmethod for Factory Methods
class Product:
"""Product with factory methods."""
def __init__(self, name: str, price: float, category: str):
self.name = name
self.price = price
self.category = category
def __repr__(self):
return f"Product({self.name!r}, ${self.price:.2f}, {self.category})"
# Factory method: create from dictionary
@classmethod
def from_dict(cls, data: dict):
"""Create Product from a dictionary."""
return cls(
name=data["name"],
price=data["price"],
category=data.get("category", "general")
)
# Factory method: create from string
@classmethod
def from_string(cls, s: str):
"""Create Product from 'name:price:category' string."""
parts = s.split(":")
name = parts[0]
price = float(parts[1])
category = parts[2] if len(parts) > 2 else "general"
return cls(name, price, category)
# Factory method: create with defaults
@classmethod
def create_digital(cls, name: str, price: float):
"""Create a digital product."""
return cls(name, price, "digital")
@classmethod
def create_physical(cls, name: str, price: float):
"""Create a physical product."""
return cls(name, price, "physical")
class User:
"""User with factory methods for different user types."""
def __init__(self, username: str, email: str, role: str, is_active: bool = True):
self.username = username
self.email = email
self.role = role
self.is_active = is_active
def __repr__(self):
status = "active" if self.is_active else "inactive"
return f"User({self.username}, {self.role}, {status})"
@classmethod
def create_admin(cls, username: str, email: str):
"""Factory method for admin users."""
return cls(username, email, role="admin")
@classmethod
def create_guest(cls):
"""Factory method for guest users."""
return cls("guest", "guest@example.com", role="guest", is_active=False)
@classmethod
def from_dict(cls, data: dict):
"""Create User from dictionary."""
return cls(**data) # Unpack dict as keyword args
def main():
print("=== Factory Methods with @classmethod ===\n")
# Standard constructor
print("--- Standard Constructor ---")
p1 = Product("Laptop", 999.99, "electronics")
print(f"Standard: {p1}")
# Factory: from dictionary
print("\n--- Factory: from_dict ---")
data = {"name": "Mouse", "price": 29.99, "category": "electronics"}
p2 = Product.from_dict(data)
print(f"From dict: {p2}")
# Factory: from string
print("\n--- Factory: from_string ---")
p3 =
print(f"From string: {p3}")
p4 = Product.from_string("Sticker:2.99")
print(f"From string (default category): {p4}")
# Factory: preset categories
print("\n--- Factory: Preset Categories ---")
p5 = Product.create_digital("E-book", 14.99)
p6 = Product.create_physical("T-shirt", 24.99)
print(f"Digital: {p5}")
print(f"Physical: {p6}")
# User factory methods
print("\n--- User Factory Methods ---")
admin = User.create_admin("admin", "admin@example.com")
guest = User.create_guest()
user_data =
regular = User.from_dict(user_data)
print(f"Admin: {admin}")
print(f"Guest: {guest}")
print(f"Regular: {regular}")
print("\n=== Key Points ===")
print("""
Factory methods (@classmethod):
• Return new instances of the class
• Use cls() instead of ClassName()
• Provide alternative ways to create objects
• Can have descriptive names like from_dict, create_admin
• Handle data conversion/validation
""")
if __name__ == "__main__":
main()
# Using @classmethod for Factory Methods
class Product:
"""Product with factory methods."""
def __init__(self, name: str, price: float, category: str):
self.name = name
self.price = price
self.category = category
def __repr__(self):
return f"Product({self.name!r}, ${self.price:.2f}, {self.category})"
# Factory method: create from dictionary
@classmethod
def from_dict(cls, data: dict):
"""Create Product from a dictionary."""
return cls(
name=data["name"],
price=data["price"],
category=data.get("category", "general")
)
# Factory method: create from string
@classmethod
def from_string(cls, s: str):
"""Create Product from 'name:price:category' string."""
parts = s.split(":")
name = parts[0]
price = float(parts[1])
category = parts[2] if len(parts) > 2 else "general"
return cls(name, price, category)
# Factory method: create with defaults
@classmethod
def create_digital(cls, name: str, price: float):
"""Create a digital product."""
return cls(name, price, "digital")
@classmethod
def create_physical(cls, name: str, price: float):
"""Create a physical product."""
return cls(name, price, "physical")
class User:
"""User with factory methods for different user types."""
def __init__(self, username: str, email: str, role: str, is_active: bool = True):
self.username = username
self.email = email
self.role = role
self.is_active = is_active
def __repr__(self):
status = "active" if self.is_active else "inactive"
return f"User({self.username}, {self.role}, {status})"
@classmethod
def create_admin(cls, username: str, email: str):
"""Factory method for admin users."""
return cls(username, email, role="admin")
@classmethod
def create_guest(cls):
"""Factory method for guest users."""
return cls("guest", "guest@example.com", role="guest", is_active=False)
@classmethod
def from_dict(cls, data: dict):
"""Create User from dictionary."""
return cls(**data) # Unpack dict as keyword args
def main():
print("=== Factory Methods with @classmethod ===\n")
# Standard constructor
print("--- Standard Constructor ---")
p1 = Product("Laptop", 999.99, "electronics")
print(f"Standard: {p1}")
# Factory: from dictionary
print("\n--- Factory: from_dict ---")
data = {"name": "Mouse", "price": 29.99, "category": "electronics"}
p2 = Product.from_dict(data)
print(f"From dict: {p2}")
# Factory: from string
print("\n--- Factory: from_string ---")
p3 =
print(f"From string: {p3}")
p4 = Product.from_string("Sticker:2.99")
print(f"From string (default category): {p4}")
# Factory: preset categories
print("\n--- Factory: Preset Categories ---")
p5 = Product.create_digital("E-book", 14.99)
p6 = Product.create_physical("T-shirt", 24.99)
print(f"Digital: {p5}")
print(f"Physical: {p6}")
# User factory methods
print("\n--- User Factory Methods ---")
admin = User.create_admin("admin", "admin@example.com")
guest = User.create_guest()
user_data =
regular = User.from_dict(user_data)
print(f"Admin: {admin}")
print(f"Guest: {guest}")
print(f"Regular: {regular}")
print("\n=== Key Points ===")
print("""
Factory methods (@classmethod):
• Return new instances of the class
• Use cls() instead of ClassName()
• Provide alternative ways to create objects
• Can have descriptive names like from_dict, create_admin
• Handle data conversion/validation
""")
if __name__ == "__main__":
main()
cls(...) creates instance of whatever class called it. Works with subclasses.
Alternative constructors
Multiple ways to create objects.
# Alternative Constructors with @classmethod
from datetime import date, datetime
class Date:
"""Custom date class with alternative constructors."""
def __init__(self, year: int, month: int, day: int):
self.year = year
self.month = month
self.day = day
def __repr__(self):
return f"Date({self.year}, {self.month}, {self.day})"
def __str__(self):
return f"{self.year}-{self.month:02d}-{self.day:02d}"
# Alternative constructor: from string
@classmethod
def from_string(cls, date_string: str, sep: str = "-"):
"""Create Date from 'YYYY-MM-DD' string."""
parts = date_string.split(sep)
year, month, day = int(parts[0]), int(parts[1]), int(parts[2])
return cls(year, month, day)
# Alternative constructor: from timestamp
@classmethod
def from_timestamp(cls, timestamp: float):
"""Create Date from Unix timestamp."""
dt = datetime.fromtimestamp(timestamp)
return cls(dt.year, dt.month, dt.day)
# Alternative constructor: today
@classmethod
def today(cls):
"""Create Date for today."""
t = date.today()
return cls(t.year, t.month, t.day)
class Person:
"""Person with multiple ways to construct."""
def __init__(self, first_name: str, last_name: str, birth_year: int):
self.first_name = first_name
self.last_name = last_name
self.birth_year = birth_year
@property
def full_name(self) -> str:
return f"{self.first_name} {self.last_name}"
@property
def age(self) -> int:
return 2025 - self.birth_year
def __repr__(self):
return f"Person({self.full_name!r}, age={self.age})"
# Alternative: from full name string
@classmethod
def from_full_name(cls, full_name: str, birth_year: int):
"""Create Person from 'First Last' string."""
parts = full_name.split()
first = parts[0]
last = " ".join(parts[1:]) if len(parts) > 1 else ""
return cls(first, last, birth_year)
# Alternative: from birth date (calculate year)
@classmethod
def from_birth_date(cls, first: str, last: str, birth_date: date):
"""Create Person from birth date."""
return cls(first, last, birth_date.year)
class Rectangle:
"""Rectangle with different construction methods."""
def __init__(self, width: float, height: float):
self.width = width
self.height = height
@property
def area(self) -> float:
return self.width * self.height
def __repr__(self):
return f"Rectangle({self.width}x{self.height}, area={self.area})"
# Alternative: create square
@classmethod
def square(cls, side: float):
"""Create a square (width == height)."""
return cls(side, side)
# Alternative: from area with aspect ratio
@classmethod
def from_area(cls, area: float, aspect_ratio: float = 1.0):
"""Create Rectangle from area and aspect ratio (width/height)."""
height = (area / aspect_ratio) ** 0.5
width = height * aspect_ratio
return cls(width, height)
def main():
print("=== Alternative Constructors ===\n")
# Date constructors
print("--- Date Constructors ---")
d1 = Date(2025, 1, 15)
print(f"Standard: {d1}")
d2 = Date.from_string("2025-06-20")
print(f"From string: {d2}")
d3 = Date.from_string("2025/12/25", sep="/")
print(f"From string (custom sep): {d3}")
d4 = Date.from_timestamp(1735689600) # Jan 1, 2025
print(f"From timestamp: {d4}")
d5 = Date.today()
print(f"Today: {d5}")
# Person constructors
print("\n--- Person Constructors ---")
p1 = Person("John", "Doe", 1990)
print(f"Standard: {p1}")
p2 = Person.from_full_name("Jane Smith", 1985)
print(f"From full name: {p2}")
p3 = Person.from_birth_date("Bob", "Johnson", date(1995, 6, 15))
print(f"From birth date: {p3}")
# Rectangle constructors
print("\n--- Rectangle Constructors ---")
r1 = Rectangle(10, 5)
print(f"Standard: {r1}")
r2 = Rectangle.square(7)
print(f"Square: {r2}")
r3 = Rectangle.from_area(100, aspect_ratio=2.0)
print(f"From area (2:1): {r3}")
r4 = Rectangle.from_area(100, aspect_ratio=1.0)
print(f"From area (1:1): {r4}")
print("\n=== Key Points ===")
print("""
Alternative constructors:
• Provide different ways to create objects
• Handle format conversion (string → object)
• Create special cases (square from Rectangle)
• Perform calculations during creation
• Named clearly: from_*, create_*, today, etc.
""")
if __name__ == "__main__":
main()
from_json(), from_string(), from_file() - all return new instances.
Utility functions with @staticmethod
Helper functions that don't need class or instance.
# Using @staticmethod for Utility Functions
import re
from typing import List
class StringUtils:
"""Utility class with static methods for string operations."""
@staticmethod
def is_palindrome(s: str) -> bool:
"""Check if string is a palindrome."""
cleaned = s.lower().replace(" ", "")
return cleaned == cleaned[::-1]
@staticmethod
def count_vowels(s: str) -> int:
"""Count vowels in a string."""
return sum(1 for c in s.lower() if c in "aeiou")
@staticmethod
def is_valid_email(email: str) -> bool:
"""Check if email format is valid."""
pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
return bool(re.match(pattern, email))
@staticmethod
def slugify(s: str) -> str:
"""Convert string to URL-friendly slug."""
s = s.lower().strip()
s = re.sub(r"[^\w\s-]", "", s)
s = re.sub(r"[\s_-]+", "-", s)
return s
class MathUtils:
"""Utility class for math operations."""
@staticmethod
def factorial(n: int) -> int:
"""Calculate factorial of n."""
if n < 0:
raise ValueError("Factorial not defined for negative numbers")
result = 1
for i in range(2, n + 1):
result *= i
return result
@staticmethod
def is_prime(n: int) -> bool:
"""Check if n is prime."""
if n < 2:
return False
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
return False
return True
@staticmethod
def gcd(a: int, b: int) -> int:
"""Calculate greatest common divisor."""
while b:
a, b = b, a % b
return abs(a)
@staticmethod
def clamp(value: float, min_val: float, max_val: float) -> float:
"""Clamp value between min and max."""
return max(min_val, min(max_val, value))
class Validator:
"""Validation utility class."""
@staticmethod
def is_valid_username(username: str) -> tuple[bool, str]:
"""Validate username, return (valid, message)."""
if len(username) < 3:
return False, "Username must be at least 3 characters"
if len(username) > 20:
return False, "Username must be at most 20 characters"
if not username[0].isalpha():
return False, "Username must start with a letter"
if not re.match(r"^[a-zA-Z0-9_]+$", username):
return False, "Username can only contain letters, numbers, and underscores"
return True, "Valid username"
@staticmethod
def is_strong_password(password: str) -> tuple[bool, List[str]]:
"""Check password strength, return (strong, issues)."""
issues = []
if len(password) < 8:
issues.append("Must be at least 8 characters")
if not re.search(r"[A-Z]", password):
issues.append("Must contain uppercase letter")
if not re.search(r"[a-z]", password):
issues.append("Must contain lowercase letter")
if not re.search(r"\d", password):
issues.append("Must contain digit")
if not re.search(r"[!@#$%^&*]", password):
issues.append("Must contain special character (!@#$%^&*)")
return len(issues) == 0, issues
def main():
print("=== Static Methods for Utilities ===\n")
# String utilities
print("--- StringUtils ---")
print(f"'radar' is palindrome: {StringUtils.is_palindrome('radar')}")
print(f"'hello' is palindrome: {StringUtils.is_palindrome('hello')}")
print(f"'A man a plan a canal Panama': {StringUtils.is_palindrome('A man a plan a canal Panama')}")
print(f"\nVowels in 'hello world': {StringUtils.count_vowels('hello world')}")
print(f"\n'user@example.com' valid: {StringUtils.is_valid_email('user@example.com')}")
print(f"'invalid-email' valid: {StringUtils.is_valid_email('invalid-email')}")
print(f"\nSlugify 'Hello World!': {StringUtils.slugify('Hello World!')}")
print(f"Slugify ' My Blog Post ': {StringUtils.slugify(' My Blog Post ')}")
# Math utilities
print("\n--- MathUtils ---")
print(f"5! = {MathUtils.factorial(5)}")
primes = [n for n in range(20) if MathUtils.is_prime(n)]
print(f"Primes under 20: {primes}")
print(f"GCD(48, 18) = {MathUtils.gcd(48, 18)}")
print(f"Clamp 15 to [0, 10]: {MathUtils.clamp(15, 0, 10)}")
print(f"Clamp -5 to [0, 10]: {MathUtils.clamp(-5, 0, 10)}")
print(f"Clamp 5 to [0, 10]: {MathUtils.clamp(5, 0, 10)}")
# Validation utilities
print("\n--- Validator ---")
usernames = ["alice", "ab", "user_123", "1invalid", "valid_user_name_here_now"]
for username in usernames:
valid, message = Validator.is_valid_username(username)
status = "✓" if valid else "✗"
print(f" {status} '{username}': {message}")
print("\nPassword strength:")
passwords = ["weak", "Better123", "Strong@Pass1"]
for password in passwords:
strong, issues = Validator.is_strong_password(password)
if strong:
print(f" ✓ '{password}': Strong password")
else:
print(f" ✗ '{password}': {', '.join(issues)}")
print("\n=== Key Points ===")
print("""
@staticmethod is good for:
• Utility functions that don't need self/cls
• Functions logically related to the class
• Validation helpers
• Math/string operations
• Pure functions (no side effects)
Why use static instead of module function?
• Logical grouping (StringUtils.is_palindrome)
• Namespace organization
• Can be overridden in subclasses
""")
if __name__ == "__main__":
main()
Related utilities grouped in class namespace. Called on class, not instance.
Inheritance behavior
How these methods behave in subclasses.
# How @classmethod and @staticmethod Behave with Inheritance
class Animal:
"""Base class demonstrating method types with inheritance."""
species_count = 0
def __init__(self, name: str):
self.name = name
Animal.species_count += 1
# Regular method - uses self, instance-specific
def speak(self) -> str:
return f"{self.name} makes a sound"
# Class method - uses cls, works with inheritance
@classmethod
def create(cls, name: str):
"""Factory that works with subclasses."""
print(f"Creating {cls.__name__}")
return cls(name)
@classmethod
def describe_class(cls) -> str:
"""Describe the class - works correctly for subclasses."""
return f"Class: {cls.__name__}"
# Static method - fixed behavior, no cls
@staticmethod
def validate_name(name: str) -> bool:
"""Validate name - same for all classes."""
return len(name) >= 2 and name[0].isupper()
class Dog(Animal):
"""Dog subclass - inherits all method types."""
def speak(self) -> str:
return f"{self.name} says Woof!"
# Override static method - optional
@staticmethod
def validate_name(name: str) -> bool:
"""Dogs can have lowercase names."""
return len(name) >= 2
class Cat(Animal):
"""Cat subclass - demonstrates cls behavior."""
def speak(self) -> str:
return f"{self.name} says Meow!"
# Add Cat-specific class method
@classmethod
def create_kitten(cls, name: str):
"""Cat-specific factory method."""
return cls(f"Little {name}")
class WorkingDog(Dog):
"""Third level inheritance."""
def __init__(self, name: str, job: str):
super().__init__(name)
self.job = job
def speak(self) -> str:
return f"{self.name} the {self.job} dog says Woof!"
# Override class method
@classmethod
def create(cls, name: str, job: str = "guard"):
"""Extended factory with job parameter."""
print(f"Creating {cls.__name__} with job: {job}")
return cls(name, job)
def main():
print("=== Inheritance Behavior ===\n")
# Class method with inheritance
print("--- @classmethod with Inheritance ---")
# cls is the actual class being called on
animal = Animal.create("Generic")
dog = Dog.create("Rex")
cat = Cat.create("Whiskers")
print(f"animal type: {type(animal).__name__}")
print(f"dog type: {type(dog).__name__}")
print(f"cat type: {type(cat).__name__}")
# describe_class shows actual class
print("\n--- describe_class (cls aware) ---")
print(Animal.describe_class())
print(Dog.describe_class())
print(Cat.describe_class())
# Static method inheritance
print("\n--- @staticmethod with Inheritance ---")
# Inherited unchanged (Animal's version)
print(f"Animal.validate_name('Rex'): {Animal.validate_name('Rex')}")
print(f"Cat.validate_name('Rex'): {Cat.validate_name('Rex')}")
# Overridden (Dog has its own version)
print(f"Animal.validate_name('rex'): {Animal.validate_name('rex')}")
print(f"Dog.validate_name('rex'): {Dog.validate_name('rex')}")
# Regular method - instance specific
print("\n--- Regular Method with Inheritance ---")
print(f"animal.speak(): {animal.speak()}")
print(f"dog.speak(): {dog.speak()}")
print(f"cat.speak(): {cat.speak()}")
# Multi-level inheritance
print("\n--- Multi-level Inheritance ---")
working_dog = WorkingDog.create("Max", "police")
print(f"working_dog type: {type(working_dog).__name__}")
print(f"working_dog.speak(): {working_dog.speak()}")
# Cat-specific method
print("\n--- Subclass-specific Methods ---")
kitten = Cat.create_kitten("Fluffy")
print(f"kitten.name: {kitten.name}")
print(f"kitten.speak(): {kitten.speak()}")
# Why cls matters
print("\n--- Why cls Matters ---")
print("""
@classmethod factory pattern:
# In Animal.create():
def create(cls, name):
return cls(name) # cls changes based on call
Animal.create("X") # cls = Animal, returns Animal
Dog.create("X") # cls = Dog, returns Dog
Cat.create("X") # cls = Cat, returns Cat
If we used Animal(name) instead of cls(name),
Dog.create() would return Animal, not Dog!
""")
print("=== Key Points ===")
print("""
@classmethod:
• cls changes based on which class calls it
• Subclasses automatically get correct behavior
• Use for factory methods, alternative constructors
@staticmethod:
• Inherited but can be overridden
• Same behavior regardless of class (unless overridden)
• Use for utility functions
Regular methods:
• Override in subclasses for polymorphism
• self is always the instance
""")
if __name__ == "__main__":
main()
@classmethod: cls is the actual subclass. @staticmethod: same function.
Exercise: practical.py
Build a data model with factory methods and utilities