Skip to main content

Static Methods & Class Variables

Introduction​

In Python, classes can have static methods, class methods, and class variables. Unlike instance members (which belong to objects created from the class), these members belong to the class itself.

Static and class methods are useful when functionality is shared across all instances, or when the behavior does not depend on any particular object instance. Think of them as utilities or class-wide state, rather than per-object data.


Concepts​

Class Variables

  • Belong to the class, not its instances.
  • Shared among all instances of the class.
  • Often used as counters or configuration values.

Static Methods (@staticmethod)

  • Declared using the @staticmethod decorator.
  • Don't receive self or cls as first parameter.
  • Cannot access instance or class attributes directly.
  • Behave like regular functions but belong to the class namespace.

Class Methods (@classmethod)

  • Declared using the @classmethod decorator.
  • Receive cls (the class) as the first parameter.
  • Can access class variables and other class methods.
  • Often used for alternative constructors.

Examples​

class TemperatureConverter:
# Class variable
conversion_count = 0

@staticmethod
def celsius_to_fahrenheit(celsius):
TemperatureConverter.conversion_count += 1
return (celsius * 9) / 5 + 32

@staticmethod
def fahrenheit_to_celsius(fahrenheit):
TemperatureConverter.conversion_count += 1
return (fahrenheit - 32) * 5 / 9

@classmethod
def get_conversion_count(cls):
return cls.conversion_count

@classmethod
def reset_counter(cls):
cls.conversion_count = 0

print(TemperatureConverter.celsius_to_fahrenheit(0)) # 32.0
print(TemperatureConverter.fahrenheit_to_celsius(32)) # 0.0
print(TemperatureConverter.get_conversion_count()) # 2

class BankAccount:
account_count = 0 # Class variable
bank_name = "Python Bank" # Class variable

def __init__(self, owner, starting_balance=0):
self.owner = owner # Instance variable
self.__balance = starting_balance
BankAccount.account_count += 1 # Modify class variable
self.account_id = BankAccount.account_count

def get_balance(self):
return self.__balance

@classmethod
def get_total_accounts(cls):
return cls.account_count

@classmethod
def get_bank_name(cls):
return cls.bank_name

@staticmethod
def calculate_interest(principal, rate, time):
"""Calculate simple interest - doesn't need instance or class data"""
return principal * rate * time

# Usage
acc1 = BankAccount("Alice", 1000)
acc2 = BankAccount("Bob", 500)

print(BankAccount.get_total_accounts()) # 2
print(BankAccount.get_bank_name()) # Python Bank
print(BankAccount.calculate_interest(1000, 0.05, 2)) # 100.0

Challenges​

1. MathHelper with static methods​

  • Create a MathHelper class with static methods add(a, b), subtract(a, b), and multiply(a, b).
💡 Show Answer
class MathHelper:
@staticmethod
def add(a, b):
return a + b

@staticmethod
def subtract(a, b):
return a - b

@staticmethod
def multiply(a, b):
return a * b

@staticmethod
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b

@staticmethod
def power(base, exponent):
return base ** exponent

# Test the static methods
print(MathHelper.add(5, 3)) # 8
print(MathHelper.subtract(10, 4)) # 6
print(MathHelper.multiply(3, 7)) # 21
print(MathHelper.divide(15, 3)) # 5.0
print(MathHelper.power(2, 8)) # 256

# Can also call on instances (not recommended but works)
helper = MathHelper()
print(helper.add(2, 3)) # 5 (works but not recommended)

2. Add pi class variable​

  • Add a class variable pi = 3.14159 to MathHelper and use it to calculate the circumference of a circle.
💡 Show Answer
class MathHelper:
pi = 3.14159265359 # Class variable

@staticmethod
def add(a, b):
return a + b

@staticmethod
def subtract(a, b):
return a - b

@staticmethod
def multiply(a, b):
return a * b

@classmethod
def circumference(cls, radius):
return 2 * cls.pi * radius

@classmethod
def area_circle(cls, radius):
return cls.pi * radius ** 2

@staticmethod
def area_rectangle(length, width):
return length * width

# Test the methods
radius = 5
print(f"Circumference: {MathHelper.circumference(radius)}") # 31.41592653589793
print(f"Area of circle: {MathHelper.area_circle(radius)}") # 78.53981633974483
print(f"Rectangle area: {MathHelper.area_rectangle(4, 6)}") # 24

# Access class variable directly
print(f"Pi value: {MathHelper.pi}")

# Using math module for comparison
import math
print(f"Built-in pi: {math.pi}")
print(f"Circumference with math.pi: {2 * math.pi * radius}")

3. Counter class​

  • Write a Counter class that has a class variable count.
  • Each time you create a new instance, increase the count.
  • Add a class method get_count() to return it.
💡 Show Answer
class Counter:
count = 0 # Class variable

def __init__(self, name="Counter"):
Counter.count += 1
self.name = name
self.instance_number = Counter.count

@classmethod
def get_count(cls):
return cls.count

@classmethod
def reset_count(cls):
cls.count = 0

def __str__(self):
return f"{self.name} #{self.instance_number}"

# Test the counter
print(f"Initial count: {Counter.get_count()}") # 0

c1 = Counter("First")
print(f"After c1: {Counter.get_count()}") # 1
print(c1) # First #1

c2 = Counter("Second")
c3 = Counter("Third")
print(f"After c2, c3: {Counter.get_count()}") # 3
print(c2) # Second #2
print(c3) # Third #3

# Reset counter
Counter.reset_count()
print(f"After reset: {Counter.get_count()}") # 0

c4 = Counter("Fourth")
print(f"New counter: {c4}") # Fourth #1 (count starts from reset)

4. Config class​

  • Create a Config class with class variables for app configuration.
  • Access them without creating an instance.
💡 Show Answer
class Config:
app_name = "MyPythonApp"
version = "1.0.0"
debug_mode = False
max_connections = 100

@classmethod
def get_app_info(cls):
return f"{cls.app_name} v{cls.version}"

@classmethod
def enable_debug(cls):
cls.debug_mode = True

@classmethod
def disable_debug(cls):
cls.debug_mode = False

@classmethod
def set_max_connections(cls, max_conn):
if max_conn > 0:
cls.max_connections = max_conn
else:
raise ValueError("Max connections must be positive")

@classmethod
def get_all_settings(cls):
return {
"app_name": cls.app_name,
"version": cls.version,
"debug_mode": cls.debug_mode,
"max_connections": cls.max_connections
}

# Access class variables without creating an instance
print(Config.app_name) # MyPythonApp
print(Config.get_app_info()) # MyPythonApp v1.0.0
print(f"Debug mode: {Config.debug_mode}") # Debug mode: False

# Modify class variables
Config.enable_debug()
print(f"Debug mode: {Config.debug_mode}") # Debug mode: True

Config.set_max_connections(200)
print(f"Max connections: {Config.max_connections}") # Max connections: 200

# Get all settings
print("All settings:")
for key, value in Config.get_all_settings().items():
print(f" {key}: {value}")

# Note: Class variables are shared across instances
config1 = Config()
config2 = Config()
print(f"Instance 1 app_name: {config1.app_name}")
print(f"Instance 2 app_name: {config2.app_name}")

# Changing class variable affects all instances
Config.app_name = "UpdatedApp"
print(f"After change - Instance 1: {config1.app_name}")
print(f"After change - Instance 2: {config2.app_name}")

5. Person with class statistics​

  • Create a Person class that tracks total people created and average age.
💡 Show Answer
class Person:
total_people = 0
total_age = 0

def __init__(self, name, age):
self.name = name
self.age = age
Person.total_people += 1
Person.total_age += age

@classmethod
def get_total_people(cls):
return cls.total_people

@classmethod
def get_average_age(cls):
if cls.total_people == 0:
return 0
return cls.total_age / cls.total_people

@classmethod
def get_statistics(cls):
return {
"total_people": cls.total_people,
"average_age": cls.get_average_age()
}

def have_birthday(self):
"""Increment age and update class total"""
Person.total_age += 1
self.age += 1

def __str__(self):
return f"{self.name} (age {self.age})"

# Test the class
print("Initial stats:", Person.get_statistics())

p1 = Person("Alice", 25)
p2 = Person("Bob", 30)
p3 = Person("Carol", 20)

print(f"Total people: {Person.get_total_people()}") # 3
print(f"Average age: {Person.get_average_age():.1f}") # 25.0

# Birthday
p1.have_birthday()
print(f"After Alice's birthday: {Person.get_average_age():.1f}") # 25.3

print("Final stats:", Person.get_statistics())
for person in [p1, p2, p3]:
print(person)

6. Database connection pool​

  • Create a DatabaseConnection class that manages a pool of connections.
💡 Show Answer
class DatabaseConnection:
max_connections = 10
active_connections = 0
connection_pool = []
connection_id_counter = 0

def __init__(self, database_name):
if DatabaseConnection.active_connections >= DatabaseConnection.max_connections:
raise Exception("Maximum connections reached")

DatabaseConnection.connection_id_counter += 1
self.connection_id = DatabaseConnection.connection_id_counter
self.database_name = database_name
self.is_connected = True

DatabaseConnection.active_connections += 1
DatabaseConnection.connection_pool.append(self)

def disconnect(self):
if self.is_connected:
self.is_connected = False
DatabaseConnection.active_connections -= 1
DatabaseConnection.connection_pool.remove(self)

@classmethod
def get_active_connections(cls):
return cls.active_connections

@classmethod
def get_max_connections(cls):
return cls.max_connections

@classmethod
def set_max_connections(cls, max_conn):
cls.max_connections = max_conn

@classmethod
def get_connection_pool_info(cls):
return [
f"Connection {conn.connection_id}: {conn.database_name}"
for conn in cls.connection_pool
]

@staticmethod
def validate_database_name(name):
"""Static method to validate database name"""
if not name or not name.isalnum():
return False
return len(name) >= 3

def __str__(self):
status = "Connected" if self.is_connected else "Disconnected"
return f"Connection {self.connection_id} to {self.database_name} - {status}"

# Test the connection pool
print(f"Max connections: {DatabaseConnection.get_max_connections()}")
print(f"Active connections: {DatabaseConnection.get_active_connections()}")

# Validate database names
print(f"Valid 'mydb': {DatabaseConnection.validate_database_name('mydb')}") # True
print(f"Valid 'x': {DatabaseConnection.validate_database_name('x')}") # False

# Create connections
conn1 = DatabaseConnection("users")
conn2 = DatabaseConnection("products")
conn3 = DatabaseConnection("orders")

print(f"\nActive connections: {DatabaseConnection.get_active_connections()}") # 3
print("Connection pool:")
for info in DatabaseConnection.get_connection_pool_info():
print(f" {info}")

# Disconnect one
conn2.disconnect()
print(f"\nAfter disconnect: {DatabaseConnection.get_active_connections()}") # 2

# Try to create too many connections
DatabaseConnection.set_max_connections(3)
try:
# We already have 2 active, max is 3, so one more should work
conn4 = DatabaseConnection("analytics")
print(f"Created connection 4: {conn4}")

# This should fail
conn5 = DatabaseConnection("backup")
except Exception as e:
print(f"Error creating connection 5: {e}")

print(f"\nFinal active connections: {DatabaseConnection.get_active_connections()}")

7. Factory pattern with class methods​

  • Create a Vehicle class with class methods for creating different types of vehicles.
💡 Show Answer
class Vehicle:
vehicle_count = 0

def __init__(self, vehicle_type, make, model, year):
self.vehicle_type = vehicle_type
self.make = make
self.model = model
self.year = year
Vehicle.vehicle_count += 1
self.vehicle_id = Vehicle.vehicle_count

@classmethod
def create_car(cls, make, model, year):
return cls("Car", make, model, year)

@classmethod
def create_truck(cls, make, model, year):
return cls("Truck", make, model, year)

@classmethod
def create_motorcycle(cls, make, model, year):
return cls("Motorcycle", make, model, year)

@classmethod
def get_vehicle_count(cls):
return cls.vehicle_count

@staticmethod
def calculate_age(year):
from datetime import datetime
current_year = datetime.now().year
return current_year - year

def get_info(self):
age = self.calculate_age(self.year)
return f"{self.vehicle_type} #{self.vehicle_id}: {self.year} {self.make} {self.model} ({age} years old)"

def __str__(self):
return self.get_info()

# Test the factory methods
car = Vehicle.create_car("Toyota", "Camry", 2020)
truck = Vehicle.create_truck("Ford", "F-150", 2019)
motorcycle = Vehicle.create_motorcycle("Honda", "CBR600", 2021)

print(car) # Car #1: 2020 Toyota Camry (4 years old)
print(truck) # Truck #2: 2019 Ford F-150 (5 years old)
print(motorcycle) # Motorcycle #3: 2021 Honda CBR600 (3 years old)

print(f"\nTotal vehicles created: {Vehicle.get_vehicle_count()}")

# Static method usage
print(f"Age of 2015 vehicle: {Vehicle.calculate_age(2015)} years")

# Alternative constructor pattern
class Product:
tax_rate = 0.08 # Class variable

def __init__(self, name, price, category="general"):
self.name = name
self.price = price
self.category = category

@classmethod
def from_string(cls, product_string):
"""Create product from string format: 'name,price,category'"""
parts = product_string.split(',')
name = parts[0].strip()
price = float(parts[1].strip())
category = parts[2].strip() if len(parts) > 2 else "general"
return cls(name, price, category)

@classmethod
def electronics(cls, name, price):
"""Factory method for electronics"""
return cls(name, price, "electronics")

@classmethod
def clothing(cls, name, price):
"""Factory method for clothing"""
return cls(name, price, "clothing")

def get_price_with_tax(self):
return self.price * (1 + self.tax_rate)

def __str__(self):
return f"{self.name} ({self.category}): ${self.price:.2f}"

# Test alternative constructors
laptop = Product.electronics("Laptop", 999.99)
shirt = Product.clothing("T-Shirt", 19.99)
book = Product.from_string("Python Guide, 29.99, books")

print(f"\n{laptop}")
print(f"With tax: ${laptop.get_price_with_tax():.2f}")

print(f"\n{shirt}")
print(f"With tax: ${shirt.get_price_with_tax():.2f}")

print(f"\n{book}")
print(f"With tax: ${book.get_price_with_tax():.2f}")

8. Utility classes comparison​

  • Compare static methods vs class methods vs instance methods.
💡 Show Answer
class MathOperations:
precision = 2 # Class variable
operation_count = 0 # Class variable to track operations

def __init__(self, name="Calculator"):
self.name = name
self.personal_count = 0 # Instance variable

@staticmethod
def add_static(a, b):
"""Static method: doesn't access class or instance data"""
return a + b

@classmethod
def add_class(cls, a, b):
"""Class method: can access class variables"""
result = a + b
cls.operation_count += 1
return round(result, cls.precision)

def add_instance(self, a, b):
"""Instance method: can access both instance and class data"""
result = a + b
self.personal_count += 1
MathOperations.operation_count += 1
print(f"{self.name} performed operation #{self.personal_count}")
return round(result, self.precision)

@classmethod
def get_total_operations(cls):
return cls.operation_count

@classmethod
def set_precision(cls, precision):
cls.precision = precision

def get_personal_count(self):
return self.personal_count

# Demonstrate the differences
print("=== Static Method ===")
result1 = MathOperations.add_static(3.14159, 2.71828)
print(f"Static result: {result1}")
print(f"Operations count: {MathOperations.get_total_operations()}") # 0

print("\n=== Class Method ===")
result2 = MathOperations.add_class(3.14159, 2.71828)
print(f"Class result: {result2}")
print(f"Operations count: {MathOperations.get_total_operations()}") # 1

print("\n=== Instance Method ===")
calc1 = MathOperations("Calculator-1")
calc2 = MathOperations("Calculator-2")

result3 = calc1.add_instance(3.14159, 2.71828)
print(f"Instance result: {result3}")
print(f"Total operations: {MathOperations.get_total_operations()}") # 2
print(f"calc1 personal count: {calc1.get_personal_count()}") # 1

result4 = calc2.add_instance(1.1, 2.2)
result5 = calc2.add_instance(3.3, 4.4)
print(f"Total operations: {MathOperations.get_total_operations()}") # 4
print(f"calc2 personal count: {calc2.get_personal_count()}") # 2

# Change class variable affects all
MathOperations.set_precision(4)
result6 = calc1.add_instance(1.123456, 2.987654)
print(f"With new precision: {result6}")

# Summary
print("\n=== Summary ===")
print("Static methods:")
print(" - Don't receive self or cls")
print(" - Can't access instance or class variables")
print(" - Behave like regular functions")
print(" - Good for utility functions")

print("\nClass methods:")
print(" - Receive cls (the class) as first parameter")
print(" - Can access and modify class variables")
print(" - Can't access instance variables")
print(" - Good for alternative constructors and class-level operations")

print("\nInstance methods:")
print(" - Receive self (the instance) as first parameter")
print(" - Can access both instance and class variables")
print(" - Can modify instance state")
print(" - Default method type for object behavior")