Skip to content

Defining and Creating Classes as Blueprints for Objects in Python

Updated: at 04:23 AM

Classes are one of the core concepts in object-oriented programming (OOP) that act as user-defined data types and blueprints for creating program objects. Defining classes allows you to encapsulate related attributes and behaviors into a single entity. Python fully supports OOP and provides a simple, straightforward syntax for defining and instantiating classes.

This comprehensive guide will teach you everything you need to know about classes in Python. We will cover the following topics in-depth with example code snippets:

Table of Contents

Open Table of Contents

Understanding Classes and Objects

A class defines a blueprint for creating objects. It encapsulates data in the form of attributes and behaviors in the form of methods in a single structure. An object is an instance of a class created at runtime. The class is the definition, and the object is the concrete realization of that definition.

For example, consider modeling a dog using OOP concepts. The generic idea or blueprint for creating dog objects is defined in the Dog class. This class specifies details like name, age, breed, etc. as attributes. Behaviors like barking, sitting, and running are defined as methods. At runtime, we can instantiate individual dog objects, like tom and jerry, based on the Dog class blueprint. Each dog object will have its own distinct name, age, and attributes as specified during object creation.

In Python, classes provide a means of bundling data and functionality together into a single neat package which serves as a blueprint for creating copies of itself in the form of objects. This approach produces modular, reusable code through encapsulation of complex data structures and behaviors into a single cohesive class interface.

Class Definitions Syntax

In Python, class definitions begin with the class keyword, followed by the class name and a colon. By convention, class names are written in CamelCase. The class body containing attributes and methods is indented under the class definition header.

class ClassName:

  # class body

Here is an example Dog class definition with some attributes and a simple method:

class Dog:

  # Class attribute
  species = 'mammal'

  # Initializer / Instance attributes
  def __init__(self, name, age):
    self.name = name
    self.age = age

  # instance method
  def description(self):
    return f"{self.name} is {self.age} years old"

Let’s break down the parts of this class definition:

We will expand on each of these class components in greater detail next.

Initializing Class Attributes

Class attributes are variables defined at the top level of the class body, outside of all methods. These attributes are shared by all instances of that class. For example, in the Dog class above, species is a class attribute since we want all dogs to be mammals. Class attributes can be accessed using the class name or instances:

print(Dog.species) # 'mammal'

tom = Dog('Tom', 5)
print(tom.species) # 'mammal'

Instance attributes are unique for each object created from the class. They are defined inside the __init__() constructor method and will vary for different instances. For example, name and age attributes in the Dog class above will be different for every dog object we create.

We can initialize instance attributes in a few ways:

  1. As Parameters in the __init__() Method
class Dog:

  def __init__(self, name, age):
    self.name = name
    self.age = age
  1. With Default Parameter Values
class Dog:

  def __init__(self, name='Rex', age=2):
    self.name = name
    self.age = age

This allows us to create a Dog without passing any arguments, using defaults.

  1. By Assigning Values in the Body of __init__()
class Dog:

  def __init__(self):
    self.name = 'Buddy'
    self.age = 5

The key takeaway is that instance attributes must be initialized on self inside the constructor method to be accessible on class instances.

Class vs Instance Attributes

For example:

class Dog:

  species = 'mammal' # class attribute

  def __init__(self, name, age):
    self.name = name # instance attribute
    self.age = age # instance attribute

This differentiation allows attributes that are common across a class to be shared efficiently, while attributes that vary across instances are allocated separately for each object.

Accessing and Modifying Attributes

Instance attributes can be accessed and modified using dot notation on the object. For example, if we created a Dog instance tom:

tom = Dog('Tom', 5)

print(tom.name) # Tom
print(tom.age) # 5

tom.age = 6 # modify age

print(tom.age) # 6

Class attributes are also accessed via dot notation on the class or instance:

print(Dog.species) # mammal
print(tom.species) # mammal

To summarize, instance attributes are accessed via self inside class methods and via the instance object in external code. Class attributes are accessed using the class name or object name from anywhere.

Adding Methods to Classes

Along with data attributes, classes can also contain methods. Methods in a class are just functions that take the instance as the first self argument. We define class methods exactly like regular functions, but inside the class:

class Dog:

  def bark(self):
    print("Woof woof!")

  def eat(self, food):
    print(f"Ate {food}")

  def sleep(self):
    print("zzZ")

Now these methods can be called on any Dog object:

tom = Dog()

tom.bark() # Woof woof!
tom.eat('bone') # Ate bone
tom.sleep() # zzZ

The self parameter refers to the calling object, similar to this in other languages like Java or C++. Using self.name allows method code to dynamically access or modify attributes on the calling object.

Methods serve as the behaviors and functionality associated with each class instance. Defining class methods allows code reuse since the same methods are available across all objects of that class.

Magic or Dunder Methods

Python has a number of built-in magic methods like __init__() (constructor), __str__() (string representation), __len__() (length), etc. These double-underscore methods have special meanings and allow classes to integrate with Python syntax and built-in functions.

Some commonly used magic methods are:

For example, we can define __str__() and __repr__() methods in the Dog class:

class Dog:
  # ...

  def __str__(self):
    return f"{self.name}, a {self.age} year old dog"

  def __repr__(self):
    return f"Dog('{self.name}', {self.age})"

tom = Dog('Tom', 6)

print(tom) # Tom, a 6 year old dog
print(repr(tom)) # Dog('Tom', 6)

This allows the Dog objects to produce useful string representations when printed or logged.

There are many other dunder methods like __add__(), __getitem__(), etc. that enable special behaviors like operator overloading and emulate container types. Mastering their usage is key to creating feature-rich classes in Python.

Class Inheritance and Polymorphism

Inheritance allows a new child class to be defined that reuses code from the parent class. This establishes an “is-a” relationship from child to parent class. For example, we can create a Labrador class inheriting from the Dog parent:

class Dog:
  # ...

class Labrador(Dog):
  # Labrador specific implementation

Child classes inherit the attributes and methods of the parent, but can also override or extend its behavior:

class Dog:

  def bark(self):
    print("Woof woof!")

  def eat(self, food):
    print("Ate food!")

class Labrador(Dog):

  def bark(self):
    print("Loud woof!")

Here, Labrador inherits methods from Dog, but provides its own implementation for bark(). This ability of a child class to extend or override parent methods is called polymorphism.

To check and leverage polymorphism, we can use isinstance() and super():

lab = Labrador()

isinstance(lab, Dog) # True

lab.bark() # Loud woof!

# Access parent method
super().bark() # Woof woof!

Inheritance allows code reuse and establishes logical hierarchies between classes (e.g. Dog -> Animal). Polymorphism provides flexibility to override parent class behavior in child classes.

Abstract Base Classes

Sometimes, we want to define a base class solely for the purpose of polymorphism and establish a common interface that child classes must implement. Such base classes are called abstract base classes in Python.

We can designate a class abstract using the abc module:

from abc import ABC, abstractmethod

class Animal(ABC):

  @abstractmethod
  def sound(self):
    pass

Now we can derive concrete, non-abstract child classes from Animal:

class Dog(Animal):

  def sound(self):
    print("Bark!")

class Cat(Animal):

  def sound(self):
    print("Meow")

Using abstract base classes ensures derived classes implement the required methods, promoting polymorphic code reuse in Python OOP.

Metaclasses

While classes define the blueprint for instance objects, metaclasses define the blueprint for classes themselves. They allow you to control class creation programmatically.

Every Python class is an instance of type by default:

print(type(Dog)) # <class 'type'>

We can also subclass type to define custom metaclasses. For example:

class CustomMeta(type):

  @classmethod
  def __prepare__(meta, name, bases):
    # Custom class body prep
    return {}

  def __new__(meta, name, bases, classdict):
    # Class creation logic
    return type.__new__(meta, name, bases, classdict)

  def __init__(cls, name, bases, classdict):
    # Extra initialization

The complex metaclasses API allows full control over class construction, letting you modify class bodies, implement custom namespaces, apply decorators or wrappers, etc.

Metaclasses provide extremely fine-grained control over Python’s class construction mechanism. While not commonly needed, they allow implementing frameworks that can fundamentally change how classes work in Python.

Conclusion

This guide gave a comprehensive overview of defining and using classes in Python, covering:

Here are some key takeways:

Equipped with these OOP concepts, you can now leverage the full power of classes in Python to write concise, flexible, and reusable code. The class paradigm is integral to Python and mastering it will enable you to model complex real-world systems with ease.