Class Attributes¶
Class attributes are shared across all instances of a class, storing data that belongs to the class itself.
What are Class Attributes¶
1. Shared Variables¶
class Student:
university = 'Yonsei' # class attribute
def __init__(self, name):
self.name = name # instance attribute
a = Student("Lee")
b = Student("Kim")
print(a.university) # 'Yonsei'
print(b.university) # 'Yonsei' - same value
2. Belong to Class¶
print(Student.university) # Access via class
3. Single Copy¶
Only one copy shared by all instances.
Defining Class Attributes¶
1. At Class Level¶
class Student:
university = 'Yonsei' # class attribute
num_students = 0 # class attribute
students_list = [] # class attribute
mandatory = ['Chapel'] # class attribute
2. Not in __init__¶
class Student:
university = 'Yonsei' # class level
def __init__(self, name):
self.name = name # instance level
3. Outside Methods¶
Defined directly in class body.
Correct Usage¶
1. Modify via Class¶
class Student:
num_students = 0
def __init__(self, name):
self.name = name
Student.num_students += 1 # Correct!
a = Student("Lee")
b = Student("Kim")
print(Student.num_students) # 2
2. Shared Lists¶
class Student:
students_list = []
def __init__(self, name):
self.name = name
Student.students_list.append(self)
3. Constants¶
class Math:
PI = 3.14159
E = 2.71828
Wrong Usage¶
1. Modifying via self¶
# WRONG
class Student:
num_students = 0
def __init__(self, name):
self.num_students += 1 # Creates instance attribute!
a = Student("Lee")
b = Student("Kim")
print(Student.num_students) # Still 0!
2. Creates Instance Attribute¶
a = Student("Lee")
print(a.__dict__) # {'name': 'Lee', 'num_students': 1}
print(Student.__dict__) # num_students still 0
3. Shadowing¶
Instance attribute shadows class attribute.
Class Attribute Use Cases¶
1. Counters¶
class Student:
count = 0
def __init__(self, name):
self.name = name
Student.count += 1
2. Shared Configuration¶
class Logger:
enable_debug = False # Shared flag
def log(self, msg):
if Logger.enable_debug:
print(msg)
3. Default Values¶
class Connection:
default_timeout = 30
def __init__(self, timeout=None):
self.timeout = timeout or Connection.default_timeout
Logger Example¶
1. Wrong: Instance Flag¶
# WRONG - each instance has own flag
class Logger:
enable_debug = False
def print(self, msg):
if self.enable_debug:
print(msg)
def set_debug(self):
self.enable_debug = True # Instance attribute!
a = Logger()
b = Logger()
a.set_debug()
a.print("hello") # Prints
b.print("hello") # Doesn't print
2. Correct: Class Method¶
# CORRECT - modify class attribute
class Logger:
enable_debug = False
def print(self, msg):
if Logger.enable_debug:
print(msg)
@classmethod
def set_debug(cls):
cls.enable_debug = True # Class attribute!
a = Logger()
b = Logger()
Logger.set_debug()
a.print("hello") # Prints
b.print("hello") # Prints
3. Affects All Instances¶
Class method modifies shared state.
Class __dict__¶
1. Read-Only Mapping¶
class Student:
university = 'Yonsei'
num_students = 0
print(Student.__dict__)
# mappingproxy({'university': 'Yonsei', 'num_students': 0, ...})
2. Access Attributes¶
print(Student.__dict__['university']) # 'Yonsei'
3. vars() on Class¶
print(vars(Student))
# Same as Student.__dict__
Special Class Attributes¶
1. __doc__¶
class Car:
"""This is a Car class."""
pass
print(Car.__doc__)
# "This is a Car class."
2. __name__¶
print(Car.__name__) # 'Car'
3. __module__¶
print(Car.__module__) # '__main__'
Instance vs Class¶
1. Instance __dict__¶
a = Student("Lee", "Math")
print(a.__dict__)
# {'name': 'Lee', 'major': 'Math'}
2. Class __dict__¶
print(Student.__dict__)
# {'university': 'Yonsei', 'num_students': 0, ...}
3. Separate Namespaces¶
Instance and class have different dictionaries.
Lookup Order¶
1. Instance First¶
class A:
x = 10
a = A()
print(a.x) # 10 - found in class
2. Then Class¶
a.x = 20 # Create instance attribute
print(a.x) # 20 - found in instance
print(A.x) # 10 - class unchanged
3. Instance Shadows Class¶
Instance attribute hides class attribute of same name.
Best Practices¶
1. Use Class Name¶
# Good
Student.num_students += 1
# Avoid
self.num_students += 1
2. Use @classmethod¶
@classmethod
def increment_count(cls):
cls.num_students += 1
3. Document Purpose¶
class Config:
"""Global configuration settings."""
DEBUG = False # Class attribute for all instances
Key Takeaways¶
- Class attributes are shared across instances.
- Modify via class name, not
self. - Using
selfcreates instance attribute. - Use
@classmethodfor class-level operations. - Class
__dict__is read-only mapping.
Runnable Example: instance_vs_class_tutorial.py¶
"""
03: Instance vs Class Attributes and Methods
Understanding the difference between instance-level and class-level members.
"""
# ============================================================================
# Example 1: Instance vs Class Attributes
class Dog:
# Class attribute - shared by ALL dogs
species = "Canis familiaris"
total_dogs = 0
def __init__(self, name, age):
# Instance attributes - unique to each dog
self.name = name
self.age = age
Dog.total_dogs += 1 # Increment class attribute
if __name__ == "__main__":
dog1 = Dog("Buddy", 3)
dog2 = Dog("Max", 5)
print("Instance attributes (different for each object):")
print(f"{dog1.name} is {dog1.age} years old")
print(f"{dog2.name} is {dog2.age} years old")
print("\nClass attribute (same for all objects):")
print(f"{dog1.name} is a {dog1.species}")
print(f"{dog2.name} is a {dog2.species}")
print(f"Total dogs created: {Dog.total_dogs}")
# ============================================================================
# Example 2: Class Methods
class Employee:
# Class attributes
company_name = "TechCorp"
employee_count = 0
raise_percentage = 1.05
def __init__(self, name, salary):
self.name = name
self.salary = salary
Employee.employee_count += 1
# Instance method (works with instance data)
def apply_raise(self):
self.salary = int(self.salary * Employee.raise_percentage)
return f"{self.name}'s new salary: ${self.salary}"
# Class method (works with class data)
@classmethod
def set_raise_percentage(cls, percentage):
cls.raise_percentage = percentage
@classmethod
def get_employee_count(cls):
return f"Total employees: {cls.employee_count}"
@classmethod
def from_string(cls, emp_string):
# Alternative constructor
name, salary = emp_string.split('-')
return cls(name, int(salary))
emp1 = Employee("Alice", 50000)
emp2 = Employee("Bob", 60000)
print(f"\n{Employee.get_employee_count()}")
print(f"{emp1.apply_raise()}")
# Using class method to change class attribute
Employee.set_raise_percentage(1.10)
print(f"{emp2.apply_raise()}")
# Using alternative constructor (class method)
emp3 = Employee.from_string("Charlie-55000")
print(f"\nCreated employee: {emp3.name} with salary ${emp3.salary}")
# ============================================================================
# Example 3: Static Methods
class MathOperations:
"""A collection of mathematical operations"""
# Static method - doesn't use instance or class data
@staticmethod
def add(x, y):
return x + y
@staticmethod
def multiply(x, y):
return x * y
@staticmethod
def is_even(number):
return number % 2 == 0
@staticmethod
def is_prime(number):
if number < 2:
return False
for i in range(2, int(number ** 0.5) + 1):
if number % i == 0:
return False
return True
# Static methods can be called without creating an instance
print(f"\n10 + 5 = {MathOperations.add(10, 5)}")
print(f"10 × 5 = {MathOperations.multiply(10, 5)}")
print(f"Is 10 even? {MathOperations.is_even(10)}")
print(f"Is 17 prime? {MathOperations.is_prime(17)}")
# ============================================================================
# Example 4: When to use what
class Pizza:
# Class attribute - shared information
menu_prices = {"small": 8.99, "medium": 12.99, "large": 16.99}
total_orders = 0
def __init__(self, size, toppings):
# Instance attributes - specific to this order
self.size = size
self.toppings = toppings
self.price = self._calculate_price()
Pizza.total_orders += 1
# Instance method - uses instance data
def _calculate_price(self):
base_price = Pizza.menu_prices[self.size]
topping_cost = len(self.toppings) * 1.50
return base_price + topping_cost
def get_description(self):
toppings_str = ", ".join(self.toppings)
return f"{self.size.capitalize()} pizza with {toppings_str} - ${self.price:.2f}"
# Class method - works with class data
@classmethod
def update_price(cls, size, new_price):
cls.menu_prices[size] = new_price
@classmethod
def get_total_orders(cls):
return cls.total_orders
# Static method - utility function, doesn't need instance or class
@staticmethod
def is_valid_size(size):
return size in ["small", "medium", "large"]
order1 = Pizza("large", ["pepperoni", "mushrooms"])
order2 = Pizza("medium", ["cheese"])
print(f"\n{order1.get_description()}")
print(f"{order2.get_description()}")
print(f"Total orders: {Pizza.get_total_orders()}")
# Using static method
print(f"\nIs 'extra-large' a valid size? {Pizza.is_valid_size('extra-large')}")
print(f"Is 'medium' a valid size? {Pizza.is_valid_size('medium')}")
# ============================================================================
# Example 5: Comparison table
print("\n" + "="*70)
print("COMPARISON: Instance vs Class vs Static")
print("="*70)
class Example:
class_var = "I am a class variable"
def __init__(self, value):
self.instance_var = value
def instance_method(self):
# Can access both instance and class variables
return f"Instance: {self.instance_var}, Class: {Example.class_var}"
@classmethod
def class_method(cls):
# Can only access class variables (no self)
return f"Class method accessing: {cls.class_var}"
@staticmethod
def static_method():
# Cannot access instance or class variables directly
return "Static method - independent function"
obj = Example("my value")
print("\nInstance Method:", obj.instance_method())
print("Class Method:", Example.class_method())
print("Static Method:", Example.static_method())
# Key Takeaways:
# 1. INSTANCE ATTRIBUTES: Unique to each object, defined with self
# 2. CLASS ATTRIBUTES: Shared by all objects, defined at class level
# 3. INSTANCE METHODS: Use 'self', access instance data
# 4. CLASS METHODS: Use '@classmethod' and 'cls', access class data
# 5. STATIC METHODS: Use '@staticmethod', don't access instance or class data
# 6. Access class attributes with ClassName.attribute or self.attribute
# 7. Class methods often used as alternative constructors
# 8. Static methods are utility functions related to the class