Skip to content

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 are shared across all instances of a class. There’s only one copy of the attribute, and all instances share that same copy.

class_attributes.py
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_count
user1 = 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 instance
print(user1.user_count) # 3 (accessing class attribute)
print(user2.user_count) # 3 (same value)
configuration_example.py
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 configuration
conn1 = 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 instances
DatabaseConnection.default_port = 3306
conn3 = DatabaseConnection("newdb")
print(conn3.get_info()) # "localhost:3306/newdb"

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.

  • Utility functions related to the class
  • Functions that don’t need instance or class data
  • Helper functions that logically belong to the class
static_methods.py
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 instance
print(User.is_valid_email("test@example.com")) # True
print(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()) # 1
math_utilities.py
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 instance
print(MathUtils.calculate_percentage(25, 100)) # 25.0
print(MathUtils.is_even(42)) # True
print(MathUtils.factorial(5)) # 120

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
class_methods.py
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 constructors
user1 = User("john", "john@example.com") # Standard constructor
user2 = User.from_email("alice@example.com") # Alternative constructor
user3 = User.from_dict({"username": "bob", "email": "bob@example.com"})
print(user2.username) # "alice"
print(user3.email) # "bob@example.com"
print(User.get_user_count()) # 3
database_models.py
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)
# Usage
user1 = User("alice", "alice@example.com")
user2 = User.create_from_json({
"username": "bob",
"email": "bob@example.com",
"id": 2
})
print(User.count()) # 2
found_user = User.find_by_email("alice@example.com")
print(found_user.username) # "alice"
FeatureClass MethodStatic Method
First Parametercls (class)None
Access to ClassYes (via cls)No (unless explicitly passed)
Access to InstanceNoNo
Use CaseFactory methods, alternative constructorsUtility functions
InheritanceWorks with subclassesDoesn’t know about subclasses
comparison.py
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 inheritance
dog1 = Dog.create_dog("Buddy") # Uses Dog class
print(dog1.species) # "Dog"
# Static method doesn't know about inheritance
print(Animal.is_mammal("Dog")) # True
print(Dog.is_mammal("Dog")) # True (same behavior)
product_example.py
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
# Usage
Product.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 rate
print(Product.format_price(laptop.price)) # Static method
electronics = Product.get_products_by_category("Electronics")
print(len(electronics)) # 1
# Alternative constructor
product_data = {"name": "Mouse", "price": 29.99, "category": "Electronics"}
mouse = Product.from_dict(product_data)

Understanding when to use class methods vs static methods helps you write more flexible and maintainable code.