All 4.1 30 min read 10 question quiz 19 exam marks

Object Oriented Programming: Classes and Objects

What a class actually is, how objects hold their own data, and why this way of organising code took over the software industry.

The idea

So far, data and the code that works on it have been separate. Object oriented programming puts them together.

A class is a blueprint. An object is a thing built from that blueprint.

Think of a class as the plan for a house and objects as the actual houses. One plan, many houses, each with its own address, its own occupants and its own front door colour.

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

    def bark(self):
        print(f"{self.name} says woof!")


rex = Dog("Rex", "Labrador")
bella = Dog("Bella", "Collie")

rex.bark()             # Rex says woof!
bella.bark()           # Bella says woof!
print(rex.breed)       # Labrador

The parts

  • class Dog: defines the blueprint. Class names use CapitalCase by convention.
  • __init__ is the constructor. It runs automatically whenever a new object is created, and it sets up that object's starting data.
  • self refers to this particular object. Every method takes it as its first parameter.
  • self.name is an attribute, a piece of data belonging to this object.
  • bark is a method, a function belonging to the class.
Why self matters

rex and bella share the same methods but have completely separate data. When rex.bark() runs, self is rex, so self.name is Rex. The same code produces different behaviour because the data is different.

A Dog class as a blueprint, and two objects made from it with their own dataA class is a blueprint and an object is a thing built from it. The class Dog has the attributes name and breed and the method bark. Two objects are created from it: rex, with the name Rex and the breed Labrador, and bella, with the name Bella and the breed Collie. They share the same methods but each object keeps its own data. Calling rex dot bark prints Rex says woof. In the code, self means this particular object, so self dot name is Rex when rex calls bark.dog.pyclass Dog:    def __init__(self, name, breed):        self.name = name        self.breed = breed    def bark(self):        print(self.name, 'says woof!')main.pyrex = Dog('Rex', 'Labrador')bella = Dog('Bella', 'Collie')rex.bark()Output: Rex says woof!class Dog: the blueprint1attributes: name breedmethod: bark()rex: Dog2self.name"Rex"self.breed"Labrador"bella: Dogself.name"Bella"self.breed"Collie"the same methods, but each object keeps its own dataclassthe blueprintobjectone thing built from itselfthis particular object3
  1. The class Dog is the blueprint: it says which attributes and methods every dog has.
  2. Each object made from it has its own attribute values. They share the methods.
  3. A class is a blueprint, an object is one thing built from it, and self means this particular object.
One plan, many houses: each house has its own address, but they are all built to the same plan.

Building a useful class

Python
class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self.balance = balance
        self.transactions = []

    def deposit(self, amount):
        if amount <= 0:
            return False
        self.balance += amount
        self.transactions.append(("deposit", amount))
        return True

    def withdraw(self, amount):
        if amount <= 0 or amount > self.balance:
            return False
        self.balance -= amount
        self.transactions.append(("withdraw", amount))
        return True

    def statement(self):
        lines = [f"Account: {self.owner}"]
        for kind, amount in self.transactions:
            lines.append(f"  {kind:<9} {amount:>8.2f}")
        lines.append(f"Balance: {self.balance:.2f}")
        return "\n".join(lines)


account = BankAccount("Aisha", 100)
account.deposit(50)
account.withdraw(30)
account.withdraw(9999)        # refused, returns False
print(account.statement())

Notice that the account cannot go overdrawn, because the rule lives inside the class alongside the data it protects. That is the real value of OOP: the object looks after its own correctness.

Special methods

Python
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return f"({self.x}, {self.y})"

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y


p = Point(3, 4)
print(p)                        # (3, 4), because of __str__
print(Point(1, 2) == Point(1, 2))   # True, because of __eq__

__str__ decides what print() shows. Without it you get something unhelpful like <__main__.Point object at 0x7f...>.

Encapsulation

Encapsulation means bundling data together with the methods that operate on it, and controlling access to that data from outside.

Why it matters

If any part of a program can reach in and change an object's data directly, the object cannot guarantee anything about itself.

Python
account.balance = -5000     # nothing stops this

Private attributes by convention

A leading underscore signals that an attribute is internal and should not be touched from outside. Two underscores makes Python actively mangle the name to discourage it.

Python
class Temperature:
    def __init__(self, celsius=0):
        self._celsius = celsius

    def get_celsius(self):
        return self._celsius

    def set_celsius(self, value):
        if value < -273.15:
            raise ValueError("Below absolute zero")
        self._celsius = value

    def get_fahrenheit(self):
        return self._celsius * 9 / 5 + 32


t = Temperature(20)
print(t.get_fahrenheit())      # 68.0
t.set_celsius(25)

Properties, the Pythonic way

Python
class Temperature:
    def __init__(self, celsius=0):
        self._celsius = celsius

    @property
    def celsius(self):
        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):
        return self._celsius * 9 / 5 + 32


t = Temperature(20)
print(t.celsius)        # 20, looks like an attribute
print(t.fahrenheit)     # 68.0, calculated on demand
t.celsius = 30          # runs the setter, which validates

This gives clean syntax with full control. The validation runs every time, but the code using it reads like a simple attribute.

The benefit in one sentence

Encapsulation means an object controls its own data, so it can guarantee that data stays valid no matter what the rest of the program does.

Encapsulation: a BankAccount keeps its balance private and offers methods to use itEncapsulation means an object controls its own data. A BankAccount object holds a private balance, here 120, inside a vault. Around the outside are three methods you can use: deposit, withdraw and get balance. Other code that tries to set the balance directly, acc dot underscore underscore balance equals minus 1000, is blocked. Other code that calls acc dot withdraw 50 gets through, because it asks the object to do it. The benefits are that only the object's own methods change the data, the methods can check, for example refusing to withdraw more than there is, so the object always stays valid, and the inside can change without breaking other code.BankAccountprivate__balance= 120deposit(amount)withdraw(amount)get_balance()Other codeacc.__balance = -1000cannot reach the dataacc.withdraw(50)asks the objectWhy it helpsThe data is privateonly the object's ownmethods change itMethods can checkwithdraw refuses ifthere is not enoughmoneyIt stays validthe balance can neverbe set to nonsenseEasy to changethe inside can changewithout breaking othercode123
  1. The data is private, inside the object.
  2. Other code cannot change it directly, but it can ask through a method.
  3. That keeps the object valid, and lets the inside change without breaking anything that uses it.
Make attributes private, and give the outside world methods that check before they change anything.

Key terms you must be able to define

Class
A blueprint defining the attributes and methods that objects of that type will have.
Object
A specific instance created from a class, with its own data.
Instance
Another word for an object created from a class.
Attribute
A piece of data belonging to an object.
Method
A function defined inside a class that operates on an object.
Constructor
The __init__ method, which runs automatically when an object is created.
self
The parameter referring to the particular object a method is operating on.
Encapsulation
Bundling data with the methods that act on it, and controlling access from outside.
Property
A method that is accessed like an attribute, allowing validation on assignment.

Flashcards

Say the definition out loud before you flip the card. Retrieving it from memory is what builds recall, reading it again is not.

DefineClass
AnswerA blueprint defining the attributes and methods that objects of that type will have.
DefineObject
AnswerA specific instance created from a class, with its own data.
DefineInstance
AnswerAnother word for an object created from a class.
DefineAttribute
AnswerA piece of data belonging to an object.
DefineMethod
AnswerA function defined inside a class that operates on an object.
DefineConstructor
AnswerThe __init__ method, which runs automatically when an object is created.
Defineself
AnswerThe parameter referring to the particular object a method is operating on.
DefineEncapsulation
AnswerBundling data with the methods that act on it, and controlling access from outside.

Reach the top grade on this topic

  • Explain the difference between a class and an object using a concrete example
  • Write a constructor that sets up every attribute the object needs
  • Put validation inside the class so the object cannot be put into an invalid state
  • Add __str__ to every class so printing an object is useful
  • Explain encapsulation as the object controlling its own data, not just as hiding

Mistakes that cost marks here

Avoid these
  • Forgetting self as the first parameter of every method.
  • Forgetting self when referring to an attribute inside a method.
  • Confusing a class with an object. The class is the blueprint, the object is built from it.
  • Putting validation in the calling code rather than inside the class, so any other caller can bypass it.
  • Defining attributes outside __init__, so different objects end up with different attributes.
Fun fact

The idea of objects came from a 1960s language called Simula, written to simulate real world systems such as ships in a harbour. Modelling real things as objects with their own state turned out to be useful for almost everything.

Knowledge check

0 of 10 answered
1What is the difference between a class and an object?
2What does __init__ do?
3What does self refer to?
4What is an attribute?
5What does __str__ control?
6What is encapsulation?
7Why put validation inside a class rather than in the code that uses it?
8What does a leading underscore on an attribute name signal?
9What does the @property decorator allow?
10Two objects created from the same class:
Score 0 / 10

Exam-style questions

Five written questions in the style of the real paper. Write a full answer, then let the marker check it against the mark scheme.

19 marks
1Explain the difference between a class and an object, using an example.3 marks
Explain

Mark scheme0 / 3
  • LA class is a template or blueprint defining attributes and methods
  • LAn object is a specific instance created from that class
  • LGives a valid example such as a Dog class with individual dogs as objects
Model answer

A class is a blueprint that defines what attributes and methods every object of that type will have, but it does not itself hold any particular data. An object is a specific instance created from that class, with its own values for each attribute. For example, a Dog class might define that every dog has a name and a breed and can bark. Creating rex = Dog("Rex", "Labrador") and bella = Dog("Bella", "Collie") produces two separate objects from that one class, sharing the same methods but each holding its own name and breed.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
2Write a class called Rectangle with a constructor taking width and height, and a method that returns the area.5 marks
Write

Mark scheme0 / 5
  • LDefines the class with a suitable name
  • LDefines __init__ with self, width and height
  • LAssigns the parameters to attributes using self
  • LDefines a method taking self
  • LReturns width multiplied by height
Model answer

class Rectangle: def __init__(self, width, height): self.width = width self.height = height def area(self): return self.width * self.height r = Rectangle(5, 3) print(r.area()) # 15 The constructor runs automatically when a Rectangle is created and stores the width and height as attributes of that particular object. The area method takes self as its first parameter so that it can access those attributes, and it returns the result rather than printing it, so the value can be used in further calculations.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
3Explain what is meant by encapsulation and why it is useful.4 marks
Explain

Mark scheme0 / 4
  • LData and the methods that operate on it are bundled together in a class
  • LAccess to the data from outside is controlled
  • LThe object can therefore validate any change to its data
  • LThis prevents other parts of the program putting the object into an invalid state
Model answer

Encapsulation means bundling an object's data together with the methods that operate on that data inside a single class, and controlling how the data can be accessed from outside. Rather than allowing any part of the program to change an attribute directly, changes are made through methods or properties belonging to the class. This is useful because it means the object can validate every change before accepting it, so a bank account can refuse a withdrawal that would take the balance below zero, and a temperature can refuse a value below absolute zero. Because the rule lives inside the class, it applies automatically everywhere the class is used, and no other part of the program can accidentally or deliberately put the object into a state that should be impossible.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
4Explain the purpose of the self parameter in a Python class.3 marks
Explain

Mark scheme0 / 3
  • LIt refers to the particular object the method is being called on
  • LIt allows the method to access that object's own attributes
  • LDifferent objects therefore produce different results from the same method
Model answer

The self parameter refers to the specific object on which a method has been called. When rex.bark() is executed, Python passes rex in as self, so inside the method self.name refers to rex's name rather than any other dog's. This is what allows every object created from the same class to share the same method code while behaving differently, because each one has its own set of attribute values. Without self, a method would have no way of knowing whose data it was supposed to be working with.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
5A class stores a student's mark. Describe how you would ensure the mark can never be set to a value outside 0 to 100.4 marks
Describe

Mark scheme0 / 4
  • LStore the mark in a private attribute with a leading underscore
  • LProvide a setter method or property to change it
  • LValidate the value inside that method before assigning it
  • LReject or raise an error for invalid values
Model answer

The mark would be stored in a private attribute such as self._mark, with the leading underscore signalling that it is internal and should not be accessed directly from outside the class. Access would then be provided through a property, with a getter that simply returns the value and a setter that checks the new value before accepting it. The setter would test whether the value is between 0 and 100 inclusive and assign it only if so, raising a ValueError otherwise. Because the check lives inside the class, every piece of code that ever sets a mark goes through it automatically, so it is impossible for any part of the program to leave a student object holding an invalid mark.

The marker looks for key phrases. If you said it another way and it is still right, give yourself the mark.
Machine mark 0 / 19