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.

basics.py
# 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.

classmethod Receives class as first argument: `@classmethod def method(cls):`.
staticmethod No self or cls: `@staticmethod def method():`. Just a function in the class.

Factory methods with @classmethod

Alternative constructors that return instances.

classmethod_factory.py
# 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.py
# 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.

staticmethod_utility.py
# 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.

inheritance_behavior.py
# 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