Difficulty: Intermediate
A class in Python is a blueprint for creating objects. It bundles data (attributes) and functionality (methods) into a single unit. You define a class with the class keyword, and by convention class names use PascalCase. Once a class is defined you create instances of it by calling the class name like a function, which triggers the special __init__ method.
The __init__ method is Python's constructor. It runs automatically every time you create a new instance and is the standard place to initialize the object's attributes. The first parameter of __init__ (and every other instance method) is self, which is a reference to the instance being created. Through self you attach data to the specific object so that each instance carries its own state.
Instance attributes are variables that belong to a particular object. They are typically defined inside __init__ using self.attribute_name = value. Because each instance has its own namespace, two objects created from the same class can hold completely different data in their attributes. This per-instance isolation is fundamental to object-oriented design.
Methods are functions defined inside a class body. Regular instance methods always take self as their first parameter, giving them access to the instance's attributes and other methods. You call a method on an object with dot notation: obj.method(). Python automatically passes the object as the self argument, so you never supply it explicitly when calling.
Beyond instance attributes, Python also supports class attributes, which are shared across all instances. A class attribute is defined directly in the class body (outside any method). When you read an attribute, Python first checks the instance namespace and falls back to the class namespace. Writing to an attribute via self always creates or updates an instance attribute, leaving the class attribute untouched for other instances.
class Dog:
species = 'Canis familiaris' # class attribute
def __init__(self, name, age):
self.name = name # instance attribute
self.age = age # instance attribute
def bark(self):
return f'{self.name} says Woof!'
def describe(self):
return f'{self.name} is {self.age} years old'
dog1 = Dog('Rex', 3)
dog2 = Dog('Buddy', 5)
print(dog1.bark())
print(dog2.describe())
print(dog1.species)
print(dog2.species)
Dog is a class with one class attribute (species) shared by all dogs and two instance attributes (name, age) unique to each dog. Methods bark() and describe() use self to access the specific instance's data.
class Counter:
count = 0 # class attribute
def __init__(self, label):
self.label = label
Counter.count += 1 # modify class attribute via class name
def display(self):
return f'{self.label}: total instances = {Counter.count}'
c1 = Counter('A')
c2 = Counter('B')
c3 = Counter('C')
print(c1.display())
print(c3.display())
print(Counter.count)
Counter.count is a class attribute modified via the class name itself. Every instance sees the same value because they all share the class namespace. Each instance has its own label attribute set through self.
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
self.balance += amount
return self.balance
def withdraw(self, amount):
if amount > self.balance:
return 'Insufficient funds'
self.balance -= amount
return self.balance
def summary(self):
return f'{self.owner}: ${self.balance}'
account = BankAccount('Alice', 100)
print(account.summary())
print(account.deposit(50))
print(account.withdraw(30))
print(account.withdraw(200))
print(account.summary())
BankAccount shows methods that modify instance state. deposit and withdraw update self.balance and return the new balance or an error message. The default parameter balance=0 lets you create an account without specifying an initial balance.
class, __init__, self, instance attributes, methods, object instantiation