Courseiva
PCAP-31-03Chapter 13 of 17Objective 5.1

Object-Oriented Programming Basics: Classes and Objects

The PCAP-31-03 exam objective 5.1 asks you to define classes and work with objects. This concept solves the problem of organising complex code by bundling data and the actions that work on that data into a single, reusable blueprint. It matters because nearly all modern Python code for real-world applications is built using these principles, and the exam will test you on exactly how to translate this idea into working Python syntax.

12 min read
Beginner
Updated Jul 23, 2026
Reviewed by Johnson Ajibi· Senior Network & Security Engineer · MSc IT Security

A simple way to picture Object-Oriented Programming Basics: Classes and Objects

The Restaurant Kitchen Analogy

A restaurant kitchen operates using a 'recipe book' and the actual meals that come out of it. The recipe book contains a set of detailed instructions for creating each dish on the menu. Each recipe specifies the required ingredients (data) and the steps to combine them (actions). The recipe for 'Margherita Pizza' is not a pizza itself; it is a blueprint that defines what a Margherita Pizza is and how to make one.

When a waiter calls in an order for 'Two Margherita Pizzas, one extra cheese, one no basil', the kitchen uses that single 'Margherita Pizza' recipe to create two distinct physical pizzas. Each pizza is a separate, tangible object. One pizza has the extra cheese; the other has no basil. They are both 'Pizza' objects, created from the same 'Pizza' recipe, but each has its own specific ingredient quantities (attributes). The chef follows the 'makePizza()' method on the recipe to produce each one. The recipe (the class) exists only as a concept. The pizzas (the objects) are what you can eat. You cannot eat a recipe, and you cannot use a pizza as a blueprint for another pizza.

This perfectly maps to object-oriented programming. The recipe is the class – the blueprint or template. The pizzas are the objects – the concrete instances created from that blueprint. The ingredients are the object's attributes (data), and the cooking steps are its methods (functions that operate on that data). Each pizza (object) is a distinct, self-contained entity with its own specific ingredient values.

How It Actually Works

At its heart, Object-Oriented Programming (OOP) is a way of organising your code. Before OOP, programmers mostly wrote 'procedural' code, which is a long list of instructions that operate on separate collections of data. If you had many things to track, you used a list of lists or a dictionary. This worked, but as programs grew larger, it became very easy to get lost. A function that supposed to update a person's age might accidentally update their name because the data was just sitting there, unprotected.

OOP introduces two fundamental concepts: the class and the object. A class is a blueprint or a template. It defines a new type of thing. Think of it as a cookie cutter. The class itself does not contain the actual data (the cookie dough). It defines the shape and what data (attributes) every object of that class will have, and what actions (methods) those objects can perform.

An object is an instance of a class. It is the actual 'thing' created using that blueprint. Using the cookie cutter analogy, each cookie you stamp out is an object. All cookies from the same cutter have the same basic shape (the same structure defined by the class), but each cookie is a separate, distinct physical item. You can decorate each cookie differently (give it different attribute values) without affecting any other cookie.

Let us look at a concrete example in Python. Imagine you are working for a library. You want to keep track of books. Before OOP, you might use a dictionary for each book:

book1 = {'title': 'The Hobbit', 'author': 'J.R.R. Tolkien', 'pages': 310}
book2 = {'title': '1984', 'author': 'George Orwell', 'pages': 328}

This works, but it has problems. If you want to add a function that 'reads' a book (prints its details), you have to write a function that expects a dictionary with exactly those keys. If someone passes a different type of dictionary by mistake, the function breaks. There is no guarantee that book1 has a 'title' key.

Now, with a class, you define the structure once.

class Book:
    def __init__(self, title, author, pages):
        self.title = title
        self.author = author
        self.pages = pages

    def display_info(self):
        return f"{self.title} by {self.author}, {self.pages} pages"

Here, class Book: is your blueprint. Inside it, __init__ is a special method called the constructor. Python runs this code automatically every time you create a new book object. The self parameter is a reference to the specific object being created. It is how each object knows its own data. self.title = title means 'take the title you were given when creating this object and store it inside this specific object's Title attribute'.

The method display_info is an action that any Book object can perform. It uses self to access its own title, author, and pages.

Now, to create an actual book object, you call the class name as if it were a function:

my_book = Book('The Hobbit', 'J.R.R. Tolkien', 310)
another_book = Book('1984', 'George Orwell', 328)

Now, my_book and another_book are distinct objects. They are both instances of the Book class. Each has its own title, author, and pages attribute. If you change my_book.title, it does not affect another_book.title. To use the method, you call it on the object:

print(my_book.display_info())

This will output: 'The Hobbit by J.R.R. Tolkien, 310 pages'.

The benefits of this approach are huge:

Encapsulation: The data (title, author, pages) and the methods that work on that data (display_info) are bundled together in one place. This makes code easier to understand and maintain.

Reusability: You define the class once and create as many objects as you need. You never have to write the display_info logic again for different books.

Organisation: Real-world programs have dozens or hundreds of classes. OOP helps you keep related code organised, making it manageable for teams.

Why does the exam care about this? Because the entire Python ecosystem is built on objects. Strings, lists, dictionaries – they are all objects. When you call my_list.append(5), you are calling a method on a list object. Understanding that append is defined inside the list class is fundamental to understanding Python. The exam tests whether you can create your own classes, instantiate objects from them, and use the attributes and methods you define.

Shows how a single Class blueprint produces multiple distinct Objects, each with their own separate instance attributes.

Walk-Through

1

1. Define the class with the class keyword

Start by writing `class ClassName:` followed by a colon. This tells Python you are creating a new blueprint. The class name conventionally uses CapitalisedWords (PascalCase). For example, `class Student:`. This step does not create any actual student yet; it only declares the pattern.

2

2. Add the __init__ method to initialise attributes

Inside the class block, define a special method named `__init__`. It must have at least one parameter `self` (which represents the object being created). Add additional parameters for the data you want to initialise, like `self.name = name`. This step happens automatically when you create a new object.

3

3. Define instance methods (actions the object can perform)

Write regular functions inside the class block. Every method must have `self` as its first parameter. These methods can read or modify the object's attributes using `self`. For example, `def introduce(self): print(f'Hi, I am {self.name}')`. These methods are the object's behaviour.

4

4. Instantiate an object from the class

Call the class name with parentheses, passing the required arguments. For example: `student1 = Student('Alice')`. Python allocates memory for a new object, calls the `__init__` method with the provided arguments, and `self` receives a reference to the new object. The variable `student1` now holds that reference.

5

5. Access the object's attributes and call its methods

Use dot notation: `student1.name` retrieves the name attribute, and `student1.introduce()` calls the introduce method. This step demonstrates that the object is a living entity with its own data and behaviour. You can create multiple objects and each accesses its own data independently.

What This Looks Like on the Job

An IT professional at a company like O'Reilly Media or Pearson might be building an online learning platform. One core requirement is managing user accounts. Instead of having a messy pile of dictionaries where keys can be misspelled, a senior developer would define a User class.

This class would contain:

Attributes like username, email, password_hash, subscription_level, last_login_date, courses_enrolled.

Methods like login(), logout(), update_profile(), enrol_in_course(), get_learning_progress().

When a new person signs up, the system calls User('john_doe', 'john@example.com', 'hashed_password'). This creates a brand-new User object stored in the database. The __init__ method ensures that every user, without exception, has all the necessary attributes created correctly. If a developer later adds a new attribute (like two_factor_enabled), they update the __init__ method, and all future objects are created with that attribute. Old objects can be updated later.

The real value appears during maintenance. Imagine a bug where a user's last_login_date is not updating correctly. An engineer can open the User class, find the login() method, and inspect exactly the three lines of code that manipulate the date. They do not have to search through 10,000 lines of unrelated code. The method is encapsulated right next to the data it uses.

Another real-world scenario is a bank's transaction system. A class called BankAccount might have:

Attributes: account_number, balance, owner_name, transaction_history.

Methods: deposit(amount), withdraw(amount), get_balance(), transfer_to(other_account, amount).

The method withdraw() would contain logic to check if the balance is sufficient before subtracting. If you tried to subtract directly (like in procedural code), you might accidentally let the balance go negative. But inside the withdraw method, the check is enforced automatically. Every time any part of the application needs to move money, it must go through that method. This centralised control is critical for preventing bugs and security issues.

In professional practice, an IT professional will:

Start by identifying the 'nouns' in the business requirement (User, Book, Account, Order, Product). These become classes.

Identify the 'verbs' (login, display, withdraw, checkout). These become methods.

Identify the descriptive characteristics (name, price, status). These become attributes.

Use version control to track changes to the class definitions so the team can review modifications.

Write unit tests that create an object, call its methods, and verify the attributes are updated correctly. This ensures the class works as expected before it is integrated into the main application.

How PCAP-31-03 Actually Tests This

The PCAP-31-03 exam objective 5.1 is very specific about what it tests. It does not test your ability to design complex object-oriented systems. It tests your ability to read and write basic Python class syntax. The exam questions fall into distinct patterns that you must master.

First, the exam loves testing the __init__ method and the self parameter. You will see code like:

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

        d = Dog('Rex')
        print(d.name)

A typical trap is the question that defines a class without an __init__ method. In Python, if you do not define __init__, a default one exists that does nothing. The object is still created. A question might ask: 'What is the output?' and the trap answer is 'Error because __init__ is missing'. The correct answer is 'The object is created successfully, and you can still add attributes later'. Python allows creating an object and then attaching attributes manually, even though it is bad practice.

Second, they test that you understand the difference between a class attribute and an instance attribute. - Class attribute: Defined directly inside the class, outside of any method. It is shared by all objects of that class. For example: class Car: wheels = 4. Every Car object shares the same wheels attribute value unless it is overridden on a specific object. - Instance attribute: Defined inside __init__ using self.attribute_name. It belongs only to the specific object. For example: self.colour = colour. Changing self.colour on one Car object does not affect another Car object.

The exam will show code where you assign a value to an attribute using self, and then ask 'Does this change affect other objects?' The answer is almost always 'No' for instance attributes and 'Yes' for class attributes (unless reassigned on the object, which creates a local instance attribute that shadows the class one).

Third, they test method calls. You must know that calling a method requires parentheses. my_object.method() is correct. my_object.method without parentheses returns a reference to the method object but does not execute it. A common trap question shows: result = obj.calculate and asks what type result is. The correct answer is 'a function object' or 'a bound method object', not the calculated value.

Fourth, the exam tests the concept of self. They will give you code that has a method defined without the self parameter. For example:

class MyClass:
    def greet(name):
        print('Hello', name)

If you try to call obj.greet('Alice'), Python will pass the object itself as the first argument to greet, so name will receive the object, not the string 'Alice'. This will cause a TypeError. The exam will ask you to identify this error.

Key definitions to memorise:

Class: A blueprint for creating objects. Defined with the class keyword.

Object (instance): A concrete entity created from a class. Created by calling the class name like a function.

Attribute: A variable that belongs to an object (instance attribute) or the class itself (class attribute). Accessed using dot notation: obj.attribute.

Method: A function defined inside a class that operates on the object's data. Always has self as the first parameter.

Constructor (__init__): A special method automatically called when an object is created. Its job is to initialise the object's attributes.

Dot notation: The syntax used to access attributes and methods of an object: object.attribute or object.method().

Exam-question types:

'What is the output of the following code?' where you trace the flow of __init__ and method calls.

'Which of the following is correct syntax for creating an object?' recognising obj = ClassName() as correct.

'Which of the following is NOT true about classes?' identifying the false statement about class attributes versus instance attributes.

'What does the self parameter represent?' multiple choice with options like 'The class', 'The current object', 'The method itself'.

Key Takeaways

A class is a blueprint for creating objects; an object is a concrete instance of a class.

The `__init__` method is a constructor that is called automatically when a new object is created, and it uses `self` to assign attributes to that specific object.

Instance attributes are unique to each object, while class attributes are shared across all objects of the same class.

Methods are functions defined inside a class that must include `self` as their first parameter to access the object's own data.

You create an object by calling the class name as if it were a function, for example `my_car = Car('red')`.

You access an object's attributes and methods using dot notation, such as `my_car.colour` and `my_car.drive()`.

Python does not enforce that `self` is named 'self', but it is a strict convention that makes code readable and predictable.

Without an explicit `__init__` method, Python provides a default one that takes no arguments and does nothing.

Easy to Mix Up

These come up on the exam all the time. Here's how to tell them apart.

Class

A blueprint or template, defined once

Does not contain actual data values until instantiated

Created using the `class` keyword

Object (Instance)

A concrete entity created from the class blueprint

Has its own copy of instance attributes with real values

Created by calling the class name as a function

Instance Attribute

Defined inside __init__ using `self.attribute`

Unique to each object; changing it on one object does not affect others

Accessed using `obj.attribute`

Class Attribute

Defined directly inside the class, outside any method

Shared across all objects of the class

Accessed using `ClassName.attribute` or via any object

Method (Instance Method)

Defined inside a class

First parameter must be `self` (or another name for the instance)

Must be called on an object, e.g., `obj.method()`

Function (Standalone)

Defined outside a class, at module level

No special parameter for an instance

Called by its name alone, e.g., `function()`

Watch Out for These

Mistake

A class is the same thing as an object, you just use the words interchangeability.

Correct

A class is a blueprint or a template. An object is a concrete instance created from that blueprint. You cannot use an object to create another object (unless you explicitly copy it), and you cannot use a class to store data directly (you need an instance of it).

The terms sound similar and new programmers often hear 'create a class' and 'create an object' used loosely. Without a concrete analogy, the distinction feels abstract.

Mistake

If I define a class, I have to define __init__, and if I don't, my code will crash when I try to create an object.

Correct

Python provides a default __init__ method automatically if you do not define one. It takes no arguments (except self) and does nothing. You can create an object of a class that has no explicit __init__ without causing an error, though the object will have no automatically assigned instance attributes.

Most tutorial examples include __init__, so beginners assume it is mandatory and a crash occurs without it. They do not realise Python provides defaults for everything.

Mistake

The 'self' parameter is a special keyword that you must name 'self'. Anything else will cause an error.

Correct

The first parameter of any instance method is a reference to the instance itself. By convention and strong recommendation, we call it 'self', but Python does not enforce that name. You could legally call it 'this' or 'me', but doing so breaks widespread convention and will confuse other programmers.

Learners see it written as 'self' in every example and assume it is a reserved keyword like 'for' or 'if'. They do not realise it is a convention with no syntactic enforcement.

Mistake

I can set an attribute that is not defined in __init__, and it will be available on all objects of that class immediately.

Correct

If you set an attribute on an object that is not defined in __init__ (e.g., `obj.new_attr = 5`), that attribute is added only to that specific object. Other existing objects of the same class will not have that attribute. Accessing it on another object will raise an AttributeError.

Beginners think 'the class defines the shape', and they forget that objects are separate. They add an attribute to one object and assume all objects now have it.

Mistake

Methods inside a class work like regular functions, so I can call them without creating an object first.

Correct

Instance methods (the normal kind) require an object to be called on. You must first create an object (`obj = MyClass()`) and then call the method on it (`obj.my_method()`). You cannot call `MyClass.my_method()` directly (without an object) unless the method is decorated as a `@classmethod` or `@staticmethod`.

Beginners see the method definition inside the class and think they can call it as a function, not realising that the first argument (`self`) is automatically supplied by Python when the method is called on an object.

Do You Actually Know This?

Reveal each answer, then mark whether you got it right. Score 60%+ to unlock the next chapter.

Frequently Asked Questions

What does `self` mean in Python?

`self` is a reference to the current object instance. When you call a method on an object, like `obj.method()`, Python automatically passes the object itself as the first argument to the method. This is why the first parameter of every instance method is `self`.

Do I have to call `__init__` manually?

No. Python calls the `__init__` method automatically when you create a new object. You never call it directly by name. You call the class like `MyClass(args)`, and Python handles the rest.

Can I add a new attribute to an object after it is created?

Yes. You can assign a new attribute to an object at any time, for example `obj.new_attr = 5`. However, this only affects that single object, not other objects of the same class. It is generally bad practice because it makes the code unpredictable.

What happens if I forget to include `self` in a method definition?

If you define a method without `self`, Python will not automatically pass the object as the first argument. When you call the method on an object, Python will try to pass the object anyway, and your method will receive the object where it expected something else, typically causing a TypeError.

What is the difference between a class attribute and an instance attribute?

A class attribute is defined directly inside the class, outside of any method, and is shared by all objects of that class. An instance attribute is defined inside `__init__` using `self` and is unique to each object. Changing an instance attribute on one object does not affect other objects.

Can I create an object without calling a special function?

You create an object by calling the class name: `obj = ClassName()`. The parentheses are the call. You cannot create an object without this syntax.

Terms Worth Knowing

Keep going

You've finished Object-Oriented Programming Basics: Classes and Objects. Continue through the PCAP-31-03 study guide to build a complete picture of the exam.

Done with this chapter?