Class-Level Features
Features that belong to the class, not the instance.
In Python, there are features that belong to the class itself rather than individual instances. These include class attributes (static attributes), static methods, and class methods.
Class Attributes (Static Attributes)
Section titled “Class Attributes (Static Attributes)”Class attributes are shared across all instances of a class. There’s only one copy of the attribute, and all instances share that same copy.
Understanding Class Attributes
Section titled “Understanding Class Attributes”class User: # Class attribute (static attribute) - shared by all instances user_count = 0
def __init__(self, username: str, email: str): # Instance attributes - unique to each instance self.username = username self.email = email User.user_count += 1 # Increment class attribute
# All instances share the same user_countuser1 = User("alice", "alice@example.com")print(User.user_count) # 1
user2 = User("bob", "bob@example.com")print(User.user_count) # 2
user3 = User("charlie", "charlie@example.com")print(User.user_count) # 3
# Access via class or instanceprint(user1.user_count) # 3 (accessing class attribute)print(user2.user_count) # 3 (same value)Real-World Example: Configuration
Section titled “Real-World Example: Configuration”class DatabaseConnection: # Class attributes for configuration default_host = "localhost" default_port = 5432 max_connections = 100 connection_count = 0
def __init__(self, database: str, host: str = None, port: int = None): self.database = database self.host = host or DatabaseConnection.default_host self.port = port or DatabaseConnection.default_port DatabaseConnection.connection_count += 1
def get_info(self): return f"{self.host}:{self.port}/{self.database}"
# All connections share the same configurationconn1 = DatabaseConnection("mydb")conn2 = DatabaseConnection("otherdb")
print(conn1.get_info()) # "localhost:5432/mydb"print(conn2.get_info()) # "localhost:5432/otherdb"print(DatabaseConnection.connection_count) # 2
# Change class attribute affects all instancesDatabaseConnection.default_port = 3306conn3 = DatabaseConnection("newdb")print(conn3.get_info()) # "localhost:3306/newdb"Static Methods
Section titled “Static Methods”Static methods belong to the class rather than any specific instance. They don’t have access to self or cls and cannot access instance or class-level data unless explicitly passed.
When to Use Static Methods
Section titled “When to Use Static Methods”- Utility functions related to the class
- Functions that don’t need instance or class data
- Helper functions that logically belong to the class
class User: user_count = 0
def __init__(self, username: str, email: str): self.username = username self.email = email User.user_count += 1
@staticmethod def get_user_count(): """Static method - doesn't need instance or class data""" return User.user_count
@staticmethod def is_valid_email(email: str) -> bool: """Static method - utility function for email validation""" return "@" in email and "." in email
@staticmethod def validate_password(password: str) -> bool: """Static method - password validation utility""" return len(password) >= 8 and any(c.isdigit() for c in password)
# Can be called on class or instanceprint(User.is_valid_email("test@example.com")) # Trueprint(User.validate_password("secure123")) # True
user = User("alice", "alice@example.com")print(user.is_valid_email("invalid")) # False (works on instance too)print(user.get_user_count()) # 1Real-World Example: Math Utilities
Section titled “Real-World Example: Math Utilities”class MathUtils: """Utility class with static methods"""
@staticmethod def calculate_percentage(part: float, total: float) -> float: """Calculate percentage""" if total == 0: return 0.0 return (part / total) * 100
@staticmethod def is_even(number: int) -> bool: """Check if number is even""" return number % 2 == 0
@staticmethod def factorial(n: int) -> int: """Calculate factorial""" if n < 0: raise ValueError("Factorial not defined for negative numbers") if n == 0 or n == 1: return 1 result = 1 for i in range(2, n + 1): result *= i return result
# Use without creating an instanceprint(MathUtils.calculate_percentage(25, 100)) # 25.0print(MathUtils.is_even(42)) # Trueprint(MathUtils.factorial(5)) # 120Class Methods
Section titled “Class Methods”Class methods take cls as the first parameter and can access class-level data. They’re often used for:
- Alternative constructors (factory methods)
- Methods that need to work with the class itself
- Factory methods that create instances in different ways
Basic Class Method
Section titled “Basic Class Method”class User: user_count = 0
def __init__(self, username: str, email: str): self.username = username self.email = email User.user_count += 1
@classmethod def from_email(cls, email: str): """Alternative constructor - create User from email""" username = email.split("@")[0] return cls(username, email)
@classmethod def from_dict(cls, data: dict): """Alternative constructor - create User from dictionary""" return cls(data["username"], data["email"])
@classmethod def get_user_count(cls): """Class method accessing class attribute""" return cls.user_count
# Use alternative constructorsuser1 = User("john", "john@example.com") # Standard constructoruser2 = User.from_email("alice@example.com") # Alternative constructoruser3 = User.from_dict({"username": "bob", "email": "bob@example.com"})
print(user2.username) # "alice"print(user3.email) # "bob@example.com"print(User.get_user_count()) # 3Real-World Example: Database Models
Section titled “Real-World Example: Database Models”class User: users = [] # Class attribute to store all users
def __init__(self, username: str, email: str, user_id: int = None): self.username = username self.email = email self.user_id = user_id or len(User.users) + 1 User.users.append(self)
@classmethod def create_from_json(cls, json_data: dict): """Create user from JSON data""" return cls( username=json_data["username"], email=json_data["email"], user_id=json_data.get("id") )
@classmethod def find_by_email(cls, email: str): """Find user by email""" for user in cls.users: if user.email == email: return user return None
@classmethod def get_all_users(cls): """Get all users""" return cls.users
@classmethod def count(cls): """Get total user count""" return len(cls.users)
# Usageuser1 = User("alice", "alice@example.com")user2 = User.create_from_json({ "username": "bob", "email": "bob@example.com", "id": 2})
print(User.count()) # 2found_user = User.find_by_email("alice@example.com")print(found_user.username) # "alice"Class Methods vs Static Methods
Section titled “Class Methods vs Static Methods”| Feature | Class Method | Static Method |
|---|---|---|
| First Parameter | cls (class) | None |
| Access to Class | Yes (via cls) | No (unless explicitly passed) |
| Access to Instance | No | No |
| Use Case | Factory methods, alternative constructors | Utility functions |
| Inheritance | Works with subclasses | Doesn’t know about subclasses |
Comparison Example
Section titled “Comparison Example”class Animal: species = "Unknown"
def __init__(self, name: str): self.name = name
@classmethod def create_dog(cls, name: str): """Class method - can access and modify class""" instance = cls(name) instance.species = "Dog" return instance
@staticmethod def is_mammal(species: str) -> bool: """Static method - utility function""" mammals = ["Dog", "Cat", "Human"] return species in mammals
class Dog(Animal): species = "Dog"
# Class method works with inheritancedog1 = Dog.create_dog("Buddy") # Uses Dog classprint(dog1.species) # "Dog"
# Static method doesn't know about inheritanceprint(Animal.is_mammal("Dog")) # Trueprint(Dog.is_mammal("Dog")) # True (same behavior)Real-World Example: E-commerce Product
Section titled “Real-World Example: E-commerce Product”class Product: # Class attributes tax_rate = 0.10 # 10% tax products = []
def __init__(self, name: str, price: float, category: str): self.name = name self.price = price self.category = category Product.products.append(self)
def calculate_total(self) -> float: """Instance method using class attribute""" return self.price * (1 + Product.tax_rate)
@classmethod def set_tax_rate(cls, rate: float): """Class method to change tax rate for all products""" cls.tax_rate = rate
@classmethod def get_products_by_category(cls, category: str): """Class method to filter products""" return [p for p in cls.products if p.category == category]
@classmethod def from_dict(cls, data: dict): """Alternative constructor""" return cls( name=data["name"], price=data["price"], category=data["category"] )
@staticmethod def format_price(price: float) -> str: """Static utility method""" return f"${price:.2f}"
@staticmethod def is_valid_category(category: str) -> bool: """Static validation method""" valid_categories = ["Electronics", "Clothing", "Food", "Books"] return category in valid_categories
# UsageProduct.set_tax_rate(0.15) # Change tax rate for all products
laptop = Product("Laptop", 999.99, "Electronics")book = Product("Python Book", 39.99, "Books")shirt = Product("T-Shirt", 19.99, "Clothing")
print(laptop.calculate_total()) # Uses class tax rateprint(Product.format_price(laptop.price)) # Static method
electronics = Product.get_products_by_category("Electronics")print(len(electronics)) # 1
# Alternative constructorproduct_data = {"name": "Mouse", "price": 29.99, "category": "Electronics"}mouse = Product.from_dict(product_data)Key Takeaways
Section titled “Key Takeaways”Understanding when to use class methods vs static methods helps you write more flexible and maintainable code.