Skip to main content

Skillber v1.0 is here!

Learn more

Classes & Objects

Checking access...

Python is an object-oriented language — everything is an object, and classes define how objects behave.

Defining a Class

class Dog:
"""A simple Dog class."""
# Class attribute — shared by all instances
species = "Canis familiaris"
# Constructor — called when creating a new instance
def __init__(self, name, age):
# Instance attributes — unique to each instance
self.name = name
self.age = age
# Instance method
def bark(self):
return f"{self.name} says woof!"
def description(self):
return f"{self.name} is {self.age} years old"

Creating and Using Objects

# Create instances
buddy = Dog("Buddy", 3)
max_dog = Dog("Max", 5)
# Access attributes
print(buddy.name) # Buddy
print(buddy.species) # Canis familiaris (class attribute)
# Call methods
print(buddy.bark()) # Buddy says woof!
print(max_dog.description()) # Max is 5 years old
# Modify attributes
buddy.age = 4
print(buddy.description()) # Buddy is 4 years old

The self Parameter

class Example:
def method(self, arg):
# self — the instance that called the method
print(f"Instance: {self}")
print(f"Arg: {arg}")
obj = Example()
obj.method("hello")
# Same as:
Example.method(obj, "hello")

Instance vs Class Attributes

class Employee:
# Class attribute — shared
company = "Tech Corp"
employee_count = 0
def __init__(self, name, salary):
# Instance attributes — unique
self.name = name
self.salary = salary
Employee.employee_count += 1
def info(self):
return f"{self.name} works at {Employee.company}"
e1 = Employee("Alice", 70000)
e2 = Employee("Bob", 80000)
print(e1.company) # Tech Corp
print(e2.company) # Tech Corp
print(Employee.company) # Tech Corp
# Modify class attribute via instance — creates instance attribute
e1.company = "Startup"
print(e1.company) # Startup — instance attribute shadows class
print(e2.company) # Tech Corp — class attribute unchanged
print(Employee.company) # Tech Corp

Tip

Access class attributes through the class name (Employee.company), not through self, to avoid accidentally creating instance attributes.

Instance Methods, Class Methods, Static Methods

class Circle:
pi = 3.14159
def __init__(self, radius):
self.radius = radius
# Instance method — works with instance data
def area(self):
return Circle.pi * self.radius ** 2
# Class method — receives class, not instance
@classmethod
def from_diameter(cls, diameter):
return cls(diameter / 2)
# Static method — no self or cls, just utility
@staticmethod
def is_valid_radius(radius):
return radius > 0
# Instance method
c = Circle(5)
print(c.area()) # 78.53975
# Class method — alternative constructor
c2 = Circle.from_diameter(10)
print(c2.radius) # 5.0
# Static method — utility function
print(Circle.is_valid_radius(-1)) # False

Property Decorators

Control attribute access with getters/setters:

class Temperature:
def __init__(self, celsius):
self._celsius = celsius
@property
def celsius(self):
"""Get temperature in Celsius."""
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("Below absolute zero!")
self._celsius = value
@property
def fahrenheit(self):
"""Get temperature in Fahrenheit (computed)."""
return (self._celsius * 9/5) + 32
temp = Temperature(25)
print(temp.celsius) # 25 — uses getter
print(temp.fahrenheit) # 77.0 — computed
temp.celsius = 30 # Uses setter with validation
# temp.celsius = -300 # ValueError!

Dunder Methods

Special methods that customize object behavior:

class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
# String representation
def __str__(self):
return f"Vector({self.x}, {self.y})"
def __repr__(self):
return f"Vector({self.x!r}, {self.y!r})"
# Arithmetic
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
# Comparison
def __eq__(self, other):
return self.x == other.x and self.y == other.y
# Length
def __abs__(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
v1 = Vector(3, 4)
v2 = Vector(1, 2)
print(v1) # Vector(3, 4) — __str__
print(v1 + v2) # Vector(4, 6) — __add__
print(v1 * 2) # Vector(6, 8) — __mul__
print(abs(v1)) # 5.0 — __abs__
print(v1 == Vector(3, 4)) # True — __eq__

Inheritance

class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("Subclass must implement")
class Cat(Animal):
def speak(self):
return f"{self.name} says meow!"
class Dog(Animal):
def speak(self):
return f"{self.name} says woof!"
animals = [Cat("Whiskers"), Dog("Buddy")]
for animal in animals:
print(animal.speak())
# Whiskers says meow!
# Buddy says woof!

Key Takeaways

  • Classes define blueprints; __init__ initializes instances
  • self refers to the instance — always the first parameter of instance methods
  • Class attributes are shared; instance attributes are unique
  • @classmethod receives the class; @staticmethod receives nothing special
  • @property creates computed attributes with getter/setter
  • Dunder methods (__str__, __add__, __eq__, etc.) customize object behavior
  • Inheritance lets subclasses extend parent behavior; super() calls parent methods