Skip to main content
Classes provide a way to bundle data and functionality together. Python’s class mechanism is powerful yet straightforward, supporting all standard features of object-oriented programming.

A Word About Names and Objects

In Python, objects have individuality, and multiple names can be bound to the same object (aliasing):
a = [1, 2, 3]
b = a  # b is an alias for a
b.append(4)
print(a)  # [1, 2, 3, 4]
This is important for mutable objects like lists and dictionaries. For immutable types (numbers, strings, tuples), aliasing doesn’t affect program behavior.

Python Scopes and Namespaces

A namespace is a mapping from names to objects. Examples include:
  • Built-in names (functions like abs(), exception names)
  • Global names in a module
  • Local names in a function
  • Attributes of an object
A scope is a textual region where a namespace is directly accessible.

Scope Example

def scope_test():
    def do_local():
        spam = "local spam"

    def do_nonlocal():
        nonlocal spam
        spam = "nonlocal spam"

    def do_global():
        global spam
        spam = "global spam"

    spam = "test spam"
    do_local()
    print("After local assignment:", spam)
    do_nonlocal()
    print("After nonlocal assignment:", spam)
    do_global()
    print("After global assignment:", spam)

scope_test()
print("In global scope:", spam)
Output:
After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam

A First Look at Classes

Class Definition Syntax

The simplest class definition:
class ClassName:
    <statement-1>
    .
    .
    .
    <statement-N>

Class Objects

Classes support two operations: attribute references and instantiation.
class MyClass:
    """A simple example class"""
    i = 12345

    def f(self):
        return 'hello world'
Attribute references:
MyClass.i  # returns 12345
MyClass.f  # returns a function object
MyClass.__doc__  # returns "A simple example class"
Instantiation:
x = MyClass()  # creates a new instance

The init Method

Customize instance creation with __init__():
class Complex:
    def __init__(self, realpart, imagpart):
        self.r = realpart
        self.i = imagpart

x = Complex(3.0, -4.5)
print(x.r, x.i)  # 3.0 -4.5

Instance Objects

Instances understand two kinds of attributes:
  1. Data attributes (instance variables)
  2. Methods (functions that belong to the object)
x.counter = 1
while x.counter < 10:
    x.counter = x.counter * 2
print(x.counter)  # 16
del x.counter

Method Objects

Methods are called on instances:
x = MyClass()
x.f()  # calls the method
You can also store method references:
xf = x.f
while True:
    print(xf())  # prints 'hello world' forever
What happens behind the scenes:
x.f()  # is equivalent to:
MyClass.f(x)
The instance is automatically passed as the first argument.

Class and Instance Variables

Class variables are shared by all instances:
class Dog:
    kind = 'canine'  # class variable shared by all instances

    def __init__(self, name):
        self.name = name  # instance variable unique to each instance

d = Dog('Fido')
e = Dog('Buddy')
print(d.kind)  # 'canine' (shared)
print(e.kind)  # 'canine' (shared)
print(d.name)  # 'Fido' (unique)
print(e.name)  # 'Buddy' (unique)
Shared mutable objects can cause problems:
class Dog:
    tricks = []  # mistaken use of a class variable

    def __init__(self, name):
        self.name = name

    def add_trick(self, trick):
        self.tricks.append(trick)

d = Dog('Fido')
e = Dog('Buddy')
d.add_trick('roll over')
e.add_trick('play dead')
print(d.tricks)  # ['roll over', 'play dead'] - unexpectedly shared!
Correct design uses instance variables:
class Dog:
    def __init__(self, name):
        self.name = name
        self.tricks = []  # creates a new empty list for each dog

    def add_trick(self, trick):
        self.tricks.append(trick)

d = Dog('Fido')
e = Dog('Buddy')
d.add_trick('roll over')
e.add_trick('play dead')
print(d.tricks)  # ['roll over']
print(e.tricks)  # ['play dead']

Inheritance

Derive new classes from existing ones:
class DerivedClassName(BaseClassName):
    <statement-1>
    .
    .
    .
    <statement-N>
Example:
class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        raise NotImplementedError("Subclass must implement abstract method")

class Dog(Animal):
    def speak(self):
        return f"{self.name} says Woof!"

class Cat(Animal):
    def speak(self):
        return f"{self.name} says Meow!"

dog = Dog("Buddy")
cat = Cat("Whiskers")
print(dog.speak())  # Buddy says Woof!
print(cat.speak())  # Whiskers says Meow!

Calling Base Class Methods

class Base:
    def greet(self):
        return "Hello from Base"

class Derived(Base):
    def greet(self):
        base_greeting = Base.greet(self)
        return f"{base_greeting} and Derived"

d = Derived()
print(d.greet())  # Hello from Base and Derived

Built-in Functions for Inheritance

isinstance(): Check an instance’s type:
isinstance(obj, int)  # True if obj is an int or subclass of int
issubclass(): Check class inheritance:
issubclass(bool, int)  # True
issubclass(float, int)  # False

Multiple Inheritance

Python supports multiple base classes:
class DerivedClassName(Base1, Base2, Base3):
    <statement-1>
    .
    .
    .
    <statement-N>
Attribute search is depth-first, left-to-right:
class A:
    def method(self):
        return "A"

class B(A):
    pass

class C(A):
    def method(self):
        return "C"

class D(B, C):
    pass

d = D()
print(d.method())  # "C" - searches B, then C

Private Variables

Python has no true private variables, but there’s a convention: Single underscore (_spam): Internal implementation detail
class MyClass:
    def __init__(self):
        self._internal_var = 42  # "internal use" convention
Name mangling (__spam): Avoid name clashes in subclasses
class Mapping:
    def __init__(self, iterable):
        self.items_list = []
        self.__update(iterable)

    def update(self, iterable):
        for item in iterable:
            self.items_list.append(item)

    __update = update   # private copy of original update() method

class MappingSubclass(Mapping):
    def update(self, keys, values):
        # provides new signature for update()
        # but does not break __init__()
        for item in zip(keys, values):
            self.items_list.append(item)
Name mangling replaces __spam with _classname__spam to avoid conflicts.

Iterators

Make your classes iterable:
class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]
Usage:
>>> rev = Reverse('spam')
>>> for char in rev:
...     print(char)
...
m
a
p
s

Generators

Generators are a simple way to create iterators:
def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]

for char in reverse('golf'):
    print(char)
# Output: f, l, o, g
Key features:
  • __iter__() and __next__() are created automatically
  • Local variables and execution state are saved between calls
  • Automatically raise StopIteration when done

Generator Expressions

Like list comprehensions but with parentheses:
>>> sum(i*i for i in range(10))  # sum of squares
285

>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec))  # dot product
260

>>> data = 'golf'
>>> list(data[i] for i in range(len(data)-1, -1, -1))
['f', 'l', 'o', 'g']

Dataclasses

Use dataclasses for simple data containers:
from dataclasses import dataclass

@dataclass
class Employee:
    name: str
    dept: str
    salary: int

john = Employee('john', 'computer lab', 1000)
print(john.dept)    # 'computer lab'
print(john.salary)  # 1000
Dataclasses automatically generate __init__(), __repr__(), and other methods.

Best Practices

Use self

Always use self as the first parameter name for instance methods

Document Classes

Use docstrings to document class purpose and usage

Favor Composition

Prefer composition over inheritance when possible

Keep It Simple

Don’t over-engineer - start simple and refactor as needed

Summary

You’ve completed the Python tutorial! You now understand:
  • Classes and objects
  • Inheritance and polymorphism
  • Special methods and protocols
  • Iterators and generators
  • Modern Python features like dataclasses
Continue learning by exploring:
  • The Python Standard Library
  • Advanced topics like decorators, context managers, and metaclasses
  • Real-world projects and contributions to open source
Happy coding!

Build docs developers (and LLMs) love